1 import os.path, re, urllib, time
3 from StringIO import StringIO
5 from twisted.application import service
6 from twisted.trial import unittest
7 from twisted.internet import defer, reactor
8 from twisted.internet.task import Clock
9 from twisted.web import client, error, http
10 from twisted.python import failure, log
12 from foolscap.api import fireEventually, flushEventualQueue
14 from nevow import rend
16 from allmydata import interfaces, uri, webish, dirnode
17 from allmydata.storage.shares import get_share_file
18 from allmydata.storage_client import StorageFarmBroker
19 from allmydata.immutable import upload
20 from allmydata.immutable.downloader.status import DownloadStatus
21 from allmydata.dirnode import DirectoryNode
22 from allmydata.nodemaker import NodeMaker
23 from allmydata.unknown import UnknownNode
24 from allmydata.web import status, common
25 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
26 from allmydata.util import fileutil, base32, hashutil
27 from allmydata.util.consumer import download_to_data
28 from allmydata.util.netstring import split_netstring
29 from allmydata.util.encodingutil import to_str
30 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
31 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
32 make_mutable_file_uri, create_mutable_filenode
33 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
34 from allmydata.mutable import servermap, publish, retrieve
35 import allmydata.test.common_util as testutil
36 from allmydata.test.no_network import GridTestMixin
37 from allmydata.test.common_web import HTTPClientGETFactory, \
39 from allmydata.client import Client, SecretHolder
40 from allmydata.introducer import IntroducerNode
42 # create a fake uploader/downloader, and a couple of fake dirnodes, then
43 # create a webserver that works against them
45 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
47 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
48 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
49 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
51 FAVICON_MARKUP = '<link href="/icon.png" rel="shortcut icon" />'
54 class FakeStatsProvider:
56 stats = {'stats': {}, 'counters': {}}
59 class FakeNodeMaker(NodeMaker):
64 'max_segment_size':128*1024 # 1024=KiB
66 def _create_lit(self, cap):
67 return FakeCHKFileNode(cap)
68 def _create_immutable(self, cap):
69 return FakeCHKFileNode(cap)
70 def _create_mutable(self, cap):
71 return FakeMutableFileNode(None,
73 self.encoding_params, None).init_from_cap(cap)
74 def create_mutable_file(self, contents="", keysize=None,
75 version=SDMF_VERSION):
76 n = FakeMutableFileNode(None, None, self.encoding_params, None)
77 return n.create(contents, version=version)
79 class FakeUploader(service.Service):
81 def upload(self, uploadable):
82 d = uploadable.get_size()
83 d.addCallback(lambda size: uploadable.read(size))
86 n = create_chk_filenode(data)
87 results = upload.UploadResults()
88 results.uri = n.get_uri()
90 d.addCallback(_got_data)
92 def get_helper_info(self):
96 def __init__(self, binaryserverid):
97 self.binaryserverid = binaryserverid
98 def get_name(self): return "short"
99 def get_longname(self): return "long"
100 def get_serverid(self): return self.binaryserverid
103 ds = DownloadStatus("storage_index", 1234)
106 serverA = FakeIServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
107 serverB = FakeIServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
108 storage_index = hashutil.storage_index_hash("SI")
109 e0 = ds.add_segment_request(0, now)
111 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
112 e1 = ds.add_segment_request(1, now+2)
114 # two outstanding requests
115 e2 = ds.add_segment_request(2, now+4)
116 e3 = ds.add_segment_request(3, now+5)
117 del e2,e3 # hush pyflakes
119 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
120 e = ds.add_segment_request(4, now)
122 e.deliver(now, 0, 140, 0.5)
124 e = ds.add_dyhb_request(serverA, now)
125 e.finished([1,2], now+1)
126 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
128 e = ds.add_read_event(0, 120, now)
129 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
131 e = ds.add_read_event(120, 30, now+2) # left unfinished
133 e = ds.add_block_request(serverA, 1, 100, 20, now)
134 e.finished(20, now+1)
135 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
137 # make sure that add_read_event() can come first too
138 ds1 = DownloadStatus(storage_index, 1234)
139 e = ds1.add_read_event(0, 120, now)
140 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
146 _all_upload_status = [upload.UploadStatus()]
147 _all_download_status = [build_one_ds()]
148 _all_mapupdate_statuses = [servermap.UpdateStatus()]
149 _all_publish_statuses = [publish.PublishStatus()]
150 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
152 def list_all_upload_statuses(self):
153 return self._all_upload_status
154 def list_all_download_statuses(self):
155 return self._all_download_status
156 def list_all_mapupdate_statuses(self):
157 return self._all_mapupdate_statuses
158 def list_all_publish_statuses(self):
159 return self._all_publish_statuses
160 def list_all_retrieve_statuses(self):
161 return self._all_retrieve_statuses
162 def list_all_helper_statuses(self):
165 class FakeClient(Client):
167 # don't upcall to Client.__init__, since we only want to initialize a
169 service.MultiService.__init__(self)
170 self.nodeid = "fake_nodeid"
171 self.nickname = "fake_nickname"
172 self.introducer_furl = "None"
173 self.stats_provider = FakeStatsProvider()
174 self._secret_holder = SecretHolder("lease secret", "convergence secret")
176 self.convergence = "some random string"
177 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
178 self.introducer_client = None
179 self.history = FakeHistory()
180 self.uploader = FakeUploader()
181 self.uploader.setServiceParent(self)
182 self.blacklist = None
183 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
186 self.mutable_file_default = SDMF_VERSION
188 def startService(self):
189 return service.MultiService.startService(self)
190 def stopService(self):
191 return service.MultiService.stopService(self)
193 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
195 class WebMixin(object):
197 self.s = FakeClient()
198 self.s.startService()
199 self.staticdir = self.mktemp()
201 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
203 self.ws.setServiceParent(self.s)
204 self.webish_port = self.ws.getPortnum()
205 self.webish_url = self.ws.getURL()
206 assert self.webish_url.endswith("/")
207 self.webish_url = self.webish_url[:-1] # these tests add their own /
209 l = [ self.s.create_dirnode() for x in range(6) ]
210 d = defer.DeferredList(l)
212 self.public_root = res[0][1]
213 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
214 self.public_url = "/uri/" + self.public_root.get_uri()
215 self.private_root = res[1][1]
219 self._foo_uri = foo.get_uri()
220 self._foo_readonly_uri = foo.get_readonly_uri()
221 self._foo_verifycap = foo.get_verify_cap().to_string()
222 # NOTE: we ignore the deferred on all set_uri() calls, because we
223 # know the fake nodes do these synchronously
224 self.public_root.set_uri(u"foo", foo.get_uri(),
225 foo.get_readonly_uri())
227 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
228 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
229 self._bar_txt_verifycap = n.get_verify_cap().to_string()
232 # XXX: Do we ever use this?
233 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
235 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
238 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
239 assert self._quux_txt_uri.startswith("URI:MDMF")
240 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
242 foo.set_uri(u"empty", res[3][1].get_uri(),
243 res[3][1].get_readonly_uri())
244 sub_uri = res[4][1].get_uri()
245 self._sub_uri = sub_uri
246 foo.set_uri(u"sub", sub_uri, sub_uri)
247 sub = self.s.create_node_from_uri(sub_uri)
250 _ign, n, blocking_uri = self.makefile(1)
251 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
253 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
254 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
255 # still think of it as an umlaut
256 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
258 self.SUBBAZ_CONTENTS, n, baz_file = self.makefile(2)
259 self._baz_file_uri = baz_file
260 sub.set_uri(u"baz.txt", baz_file, baz_file)
262 _ign, n, self._bad_file_uri = self.makefile(3)
263 # this uri should not be downloadable
264 del FakeCHKFileNode.all_contents[self._bad_file_uri]
267 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
268 rodir.get_readonly_uri())
269 rodir.set_uri(u"nor", baz_file, baz_file)
275 # public/foo/quux.txt
276 # public/foo/blockingfile
279 # public/foo/sub/baz.txt
281 # public/reedownlee/nor
282 self.NEWFILE_CONTENTS = "newfile contents\n"
284 return foo.get_metadata_for(u"bar.txt")
286 def _got_metadata(metadata):
287 self._bar_txt_metadata = metadata
288 d.addCallback(_got_metadata)
291 def makefile(self, number):
292 contents = "contents of file %s\n" % number
293 n = create_chk_filenode(contents)
294 return contents, n, n.get_uri()
296 def makefile_mutable(self, number, mdmf=False):
297 contents = "contents of mutable file %s\n" % number
298 n = create_mutable_filenode(contents, mdmf)
299 return contents, n, n.get_uri(), n.get_readonly_uri()
302 return self.s.stopService()
304 def failUnlessIsBarDotTxt(self, res):
305 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
307 def failUnlessIsQuuxDotTxt(self, res):
308 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
310 def failUnlessIsBazDotTxt(self, res):
311 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
313 def failUnlessIsSubBazDotTxt(self, res):
314 self.failUnlessReallyEqual(res, self.SUBBAZ_CONTENTS, res)
316 def failUnlessIsBarJSON(self, res):
317 data = simplejson.loads(res)
318 self.failUnless(isinstance(data, list))
319 self.failUnlessEqual(data[0], "filenode")
320 self.failUnless(isinstance(data[1], dict))
321 self.failIf(data[1]["mutable"])
322 self.failIfIn("rw_uri", data[1]) # immutable
323 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
324 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
325 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
327 def failUnlessIsQuuxJSON(self, res, readonly=False):
328 data = simplejson.loads(res)
329 self.failUnless(isinstance(data, list))
330 self.failUnlessEqual(data[0], "filenode")
331 self.failUnless(isinstance(data[1], dict))
333 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
335 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
336 self.failUnless(metadata['mutable'])
338 self.failIfIn("rw_uri", metadata)
340 self.failUnlessIn("rw_uri", metadata)
341 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
342 self.failUnlessIn("ro_uri", metadata)
343 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
344 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
346 def failUnlessIsFooJSON(self, res):
347 data = simplejson.loads(res)
348 self.failUnless(isinstance(data, list))
349 self.failUnlessEqual(data[0], "dirnode", res)
350 self.failUnless(isinstance(data[1], dict))
351 self.failUnless(data[1]["mutable"])
352 self.failUnlessIn("rw_uri", data[1]) # mutable
353 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
354 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
355 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
357 kidnames = sorted([unicode(n) for n in data[1]["children"]])
358 self.failUnlessEqual(kidnames,
359 [u"bar.txt", u"baz.txt", u"blockingfile",
360 u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
361 kids = dict( [(unicode(name),value)
363 in data[1]["children"].iteritems()] )
364 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
365 self.failUnlessIn("metadata", kids[u"sub"][1])
366 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
367 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
368 self.failUnlessIn("linkcrtime", tahoe_md)
369 self.failUnlessIn("linkmotime", tahoe_md)
370 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
371 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
372 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
373 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
374 self._bar_txt_verifycap)
375 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
376 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
377 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
378 self._bar_txt_metadata["tahoe"]["linkcrtime"])
379 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
381 self.failUnlessIn("quux.txt", kids)
382 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
384 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
385 self._quux_txt_readonly_uri)
387 def GET(self, urlpath, followRedirect=False, return_response=False,
389 # if return_response=True, this fires with (data, statuscode,
390 # respheaders) instead of just data.
391 assert not isinstance(urlpath, unicode)
392 url = self.webish_url + urlpath
393 factory = HTTPClientGETFactory(url, method="GET",
394 followRedirect=followRedirect, **kwargs)
395 reactor.connectTCP("localhost", self.webish_port, factory)
398 return (data, factory.status, factory.response_headers)
400 d.addCallback(_got_data)
401 return factory.deferred
403 def HEAD(self, urlpath, return_response=False, **kwargs):
404 # this requires some surgery, because twisted.web.client doesn't want
405 # to give us back the response headers.
406 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
407 reactor.connectTCP("localhost", self.webish_port, factory)
410 return (data, factory.status, factory.response_headers)
412 d.addCallback(_got_data)
413 return factory.deferred
415 def PUT(self, urlpath, data, **kwargs):
416 url = self.webish_url + urlpath
417 return client.getPage(url, method="PUT", postdata=data, **kwargs)
419 def DELETE(self, urlpath):
420 url = self.webish_url + urlpath
421 return client.getPage(url, method="DELETE")
423 def POST(self, urlpath, followRedirect=False, **fields):
424 sepbase = "boogabooga"
428 form.append('Content-Disposition: form-data; name="_charset"')
432 for name, value in fields.iteritems():
433 if isinstance(value, tuple):
434 filename, value = value
435 form.append('Content-Disposition: form-data; name="%s"; '
436 'filename="%s"' % (name, filename.encode("utf-8")))
438 form.append('Content-Disposition: form-data; name="%s"' % name)
440 if isinstance(value, unicode):
441 value = value.encode("utf-8")
444 assert isinstance(value, str)
451 body = "\r\n".join(form) + "\r\n"
452 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
453 return self.POST2(urlpath, body, headers, followRedirect)
455 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
456 url = self.webish_url + urlpath
457 return client.getPage(url, method="POST", postdata=body,
458 headers=headers, followRedirect=followRedirect)
460 def shouldFail(self, res, expected_failure, which,
461 substring=None, response_substring=None):
462 if isinstance(res, failure.Failure):
463 res.trap(expected_failure)
465 self.failUnlessIn(substring, str(res), which)
466 if response_substring:
467 self.failUnlessIn(response_substring, res.value.response, which)
469 self.fail("%s was supposed to raise %s, not get '%s'" %
470 (which, expected_failure, res))
472 def shouldFail2(self, expected_failure, which, substring,
474 callable, *args, **kwargs):
475 assert substring is None or isinstance(substring, str)
476 assert response_substring is None or isinstance(response_substring, str)
477 d = defer.maybeDeferred(callable, *args, **kwargs)
479 if isinstance(res, failure.Failure):
480 res.trap(expected_failure)
482 self.failUnlessIn(substring, str(res),
483 "'%s' not in '%s' for test '%s'" % \
484 (substring, str(res), which))
485 if response_substring:
486 self.failUnlessIn(response_substring, res.value.response,
487 "'%s' not in '%s' for test '%s'" % \
488 (response_substring, res.value.response,
491 self.fail("%s was supposed to raise %s, not get '%s'" %
492 (which, expected_failure, res))
496 def should404(self, res, which):
497 if isinstance(res, failure.Failure):
498 res.trap(error.Error)
499 self.failUnlessReallyEqual(res.value.status, "404")
501 self.fail("%s was supposed to Error(404), not get '%s'" %
504 def should302(self, res, which):
505 if isinstance(res, failure.Failure):
506 res.trap(error.Error)
507 self.failUnlessReallyEqual(res.value.status, "302")
509 self.fail("%s was supposed to Error(302), not get '%s'" %
513 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
514 def test_create(self):
517 def test_welcome(self):
520 self.failUnlessIn('Welcome to Tahoe-LAFS', res)
521 self.failUnlessIn(FAVICON_MARKUP, res)
522 self.failUnlessIn('href="https://tahoe-lafs.org/"', res)
524 self.s.basedir = 'web/test_welcome'
525 fileutil.make_dirs("web/test_welcome")
526 fileutil.make_dirs("web/test_welcome/private")
528 d.addCallback(_check)
531 def test_status(self):
532 h = self.s.get_history()
533 dl_num = h.list_all_download_statuses()[0].get_counter()
534 ul_num = h.list_all_upload_statuses()[0].get_counter()
535 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
536 pub_num = h.list_all_publish_statuses()[0].get_counter()
537 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
538 d = self.GET("/status", followRedirect=True)
540 self.failUnlessIn('Upload and Download Status', res)
541 self.failUnlessIn('"down-%d"' % dl_num, res)
542 self.failUnlessIn('"up-%d"' % ul_num, res)
543 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
544 self.failUnlessIn('"publish-%d"' % pub_num, res)
545 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
546 d.addCallback(_check)
547 d.addCallback(lambda res: self.GET("/status/?t=json"))
548 def _check_json(res):
549 data = simplejson.loads(res)
550 self.failUnless(isinstance(data, dict))
551 #active = data["active"]
552 # TODO: test more. We need a way to fake an active operation
554 d.addCallback(_check_json)
556 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
558 self.failUnlessIn("File Download Status", res)
559 d.addCallback(_check_dl)
560 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
561 def _check_dl_json(res):
562 data = simplejson.loads(res)
563 self.failUnless(isinstance(data, dict))
564 self.failUnlessIn("read", data)
565 self.failUnlessEqual(data["read"][0]["length"], 120)
566 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
567 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
568 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
569 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
570 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
571 # serverids[] keys are strings, since that's what JSON does, but
572 # we'd really like them to be ints
573 self.failUnlessEqual(data["serverids"]["0"], "phwr")
574 self.failUnless(data["serverids"].has_key("1"), data["serverids"])
575 self.failUnlessEqual(data["serverids"]["1"], "cmpu", data["serverids"])
576 self.failUnlessEqual(data["server_info"][phwr_id]["short"], "phwr")
577 self.failUnlessEqual(data["server_info"][cmpu_id]["short"], "cmpu")
578 self.failUnlessIn("dyhb", data)
579 self.failUnlessIn("misc", data)
580 d.addCallback(_check_dl_json)
581 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
583 self.failUnlessIn("File Upload Status", res)
584 d.addCallback(_check_ul)
585 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
586 def _check_mapupdate(res):
587 self.failUnlessIn("Mutable File Servermap Update Status", res)
588 d.addCallback(_check_mapupdate)
589 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
590 def _check_publish(res):
591 self.failUnlessIn("Mutable File Publish Status", res)
592 d.addCallback(_check_publish)
593 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
594 def _check_retrieve(res):
595 self.failUnlessIn("Mutable File Retrieve Status", res)
596 d.addCallback(_check_retrieve)
600 def test_status_numbers(self):
601 drrm = status.DownloadResultsRendererMixin()
602 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
603 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
604 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
605 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
606 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
607 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
608 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
609 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
610 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
612 urrm = status.UploadResultsRendererMixin()
613 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
614 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
615 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
616 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
617 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
618 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
619 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
620 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
621 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
623 def test_GET_FILEURL(self):
624 d = self.GET(self.public_url + "/foo/bar.txt")
625 d.addCallback(self.failUnlessIsBarDotTxt)
628 def test_GET_FILEURL_range(self):
629 headers = {"range": "bytes=1-10"}
630 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
631 return_response=True)
632 def _got((res, status, headers)):
633 self.failUnlessReallyEqual(int(status), 206)
634 self.failUnless(headers.has_key("content-range"))
635 self.failUnlessReallyEqual(headers["content-range"][0],
636 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
637 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
641 def test_GET_FILEURL_partial_range(self):
642 headers = {"range": "bytes=5-"}
643 length = len(self.BAR_CONTENTS)
644 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
645 return_response=True)
646 def _got((res, status, headers)):
647 self.failUnlessReallyEqual(int(status), 206)
648 self.failUnless(headers.has_key("content-range"))
649 self.failUnlessReallyEqual(headers["content-range"][0],
650 "bytes 5-%d/%d" % (length-1, length))
651 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
655 def test_GET_FILEURL_partial_end_range(self):
656 headers = {"range": "bytes=-5"}
657 length = len(self.BAR_CONTENTS)
658 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
659 return_response=True)
660 def _got((res, status, headers)):
661 self.failUnlessReallyEqual(int(status), 206)
662 self.failUnless(headers.has_key("content-range"))
663 self.failUnlessReallyEqual(headers["content-range"][0],
664 "bytes %d-%d/%d" % (length-5, length-1, length))
665 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
669 def test_GET_FILEURL_partial_range_overrun(self):
670 headers = {"range": "bytes=100-200"}
671 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
672 "416 Requested Range not satisfiable",
673 "First beyond end of file",
674 self.GET, self.public_url + "/foo/bar.txt",
678 def test_HEAD_FILEURL_range(self):
679 headers = {"range": "bytes=1-10"}
680 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
681 return_response=True)
682 def _got((res, status, headers)):
683 self.failUnlessReallyEqual(res, "")
684 self.failUnlessReallyEqual(int(status), 206)
685 self.failUnless(headers.has_key("content-range"))
686 self.failUnlessReallyEqual(headers["content-range"][0],
687 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
691 def test_HEAD_FILEURL_partial_range(self):
692 headers = {"range": "bytes=5-"}
693 length = len(self.BAR_CONTENTS)
694 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
695 return_response=True)
696 def _got((res, status, headers)):
697 self.failUnlessReallyEqual(int(status), 206)
698 self.failUnless(headers.has_key("content-range"))
699 self.failUnlessReallyEqual(headers["content-range"][0],
700 "bytes 5-%d/%d" % (length-1, length))
704 def test_HEAD_FILEURL_partial_end_range(self):
705 headers = {"range": "bytes=-5"}
706 length = len(self.BAR_CONTENTS)
707 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
708 return_response=True)
709 def _got((res, status, headers)):
710 self.failUnlessReallyEqual(int(status), 206)
711 self.failUnless(headers.has_key("content-range"))
712 self.failUnlessReallyEqual(headers["content-range"][0],
713 "bytes %d-%d/%d" % (length-5, length-1, length))
717 def test_HEAD_FILEURL_partial_range_overrun(self):
718 headers = {"range": "bytes=100-200"}
719 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
720 "416 Requested Range not satisfiable",
722 self.HEAD, self.public_url + "/foo/bar.txt",
726 def test_GET_FILEURL_range_bad(self):
727 headers = {"range": "BOGUS=fizbop-quarnak"}
728 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
729 return_response=True)
730 def _got((res, status, headers)):
731 self.failUnlessReallyEqual(int(status), 200)
732 self.failUnless(not headers.has_key("content-range"))
733 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
737 def test_HEAD_FILEURL(self):
738 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
739 def _got((res, status, headers)):
740 self.failUnlessReallyEqual(res, "")
741 self.failUnlessReallyEqual(headers["content-length"][0],
742 str(len(self.BAR_CONTENTS)))
743 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
747 def test_GET_FILEURL_named(self):
748 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
749 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
750 d = self.GET(base + "/@@name=/blah.txt")
751 d.addCallback(self.failUnlessIsBarDotTxt)
752 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
753 d.addCallback(self.failUnlessIsBarDotTxt)
754 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
755 d.addCallback(self.failUnlessIsBarDotTxt)
756 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
757 d.addCallback(self.failUnlessIsBarDotTxt)
758 save_url = base + "?save=true&filename=blah.txt"
759 d.addCallback(lambda res: self.GET(save_url))
760 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
761 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
762 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
763 u_url = base + "?save=true&filename=" + u_fn_e
764 d.addCallback(lambda res: self.GET(u_url))
765 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
768 def test_PUT_FILEURL_named_bad(self):
769 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
770 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
772 "/file can only be used with GET or HEAD",
773 self.PUT, base + "/@@name=/blah.txt", "")
777 def test_GET_DIRURL_named_bad(self):
778 base = "/file/%s" % urllib.quote(self._foo_uri)
779 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
782 self.GET, base + "/@@name=/blah.txt")
785 def test_GET_slash_file_bad(self):
786 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
788 "/file must be followed by a file-cap and a name",
792 def test_GET_unhandled_URI_named(self):
793 contents, n, newuri = self.makefile(12)
794 verifier_cap = n.get_verify_cap().to_string()
795 base = "/file/%s" % urllib.quote(verifier_cap)
796 # client.create_node_from_uri() can't handle verify-caps
797 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
798 "400 Bad Request", "is not a file-cap",
802 def test_GET_unhandled_URI(self):
803 contents, n, newuri = self.makefile(12)
804 verifier_cap = n.get_verify_cap().to_string()
805 base = "/uri/%s" % urllib.quote(verifier_cap)
806 # client.create_node_from_uri() can't handle verify-caps
807 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
809 "GET unknown URI type: can only do t=info",
813 def test_GET_FILE_URI(self):
814 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
816 d.addCallback(self.failUnlessIsBarDotTxt)
819 def test_GET_FILE_URI_mdmf(self):
820 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
822 d.addCallback(self.failUnlessIsQuuxDotTxt)
825 def test_GET_FILE_URI_mdmf_extensions(self):
826 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
828 d.addCallback(self.failUnlessIsQuuxDotTxt)
831 def test_GET_FILE_URI_mdmf_readonly(self):
832 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
834 d.addCallback(self.failUnlessIsQuuxDotTxt)
837 def test_GET_FILE_URI_badchild(self):
838 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
839 errmsg = "Files have no children, certainly not named 'boguschild'"
840 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
841 "400 Bad Request", errmsg,
845 def test_PUT_FILE_URI_badchild(self):
846 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
847 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
848 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
849 "400 Bad Request", errmsg,
853 def test_PUT_FILE_URI_mdmf(self):
854 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
855 self._quux_new_contents = "new_contents"
857 d.addCallback(lambda res:
858 self.failUnlessIsQuuxDotTxt(res))
859 d.addCallback(lambda ignored:
860 self.PUT(base, self._quux_new_contents))
861 d.addCallback(lambda ignored:
863 d.addCallback(lambda res:
864 self.failUnlessReallyEqual(res, self._quux_new_contents))
867 def test_PUT_FILE_URI_mdmf_extensions(self):
868 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
869 self._quux_new_contents = "new_contents"
871 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
872 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
873 d.addCallback(lambda ignored: self.GET(base))
874 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
878 def test_PUT_FILE_URI_mdmf_readonly(self):
879 # We're not allowed to PUT things to a readonly cap.
880 base = "/uri/%s" % self._quux_txt_readonly_uri
882 d.addCallback(lambda res:
883 self.failUnlessIsQuuxDotTxt(res))
884 # What should we get here? We get a 500 error now; that's not right.
885 d.addCallback(lambda ignored:
886 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
887 "400 Bad Request", "read-only cap",
888 self.PUT, base, "new data"))
891 def test_PUT_FILE_URI_sdmf_readonly(self):
892 # We're not allowed to put things to a readonly cap.
893 base = "/uri/%s" % self._baz_txt_readonly_uri
895 d.addCallback(lambda res:
896 self.failUnlessIsBazDotTxt(res))
897 d.addCallback(lambda ignored:
898 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
899 "400 Bad Request", "read-only cap",
900 self.PUT, base, "new_data"))
903 # TODO: version of this with a Unicode filename
904 def test_GET_FILEURL_save(self):
905 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
906 return_response=True)
907 def _got((res, statuscode, headers)):
908 content_disposition = headers["content-disposition"][0]
909 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
910 self.failUnlessIsBarDotTxt(res)
914 def test_GET_FILEURL_missing(self):
915 d = self.GET(self.public_url + "/foo/missing")
916 d.addBoth(self.should404, "test_GET_FILEURL_missing")
919 def test_GET_FILEURL_info_mdmf(self):
920 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
922 self.failUnlessIn("mutable file (mdmf)", res)
923 self.failUnlessIn(self._quux_txt_uri, res)
924 self.failUnlessIn(self._quux_txt_readonly_uri, res)
928 def test_GET_FILEURL_info_mdmf_readonly(self):
929 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
931 self.failUnlessIn("mutable file (mdmf)", res)
932 self.failIfIn(self._quux_txt_uri, res)
933 self.failUnlessIn(self._quux_txt_readonly_uri, res)
937 def test_GET_FILEURL_info_sdmf(self):
938 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
940 self.failUnlessIn("mutable file (sdmf)", res)
941 self.failUnlessIn(self._baz_txt_uri, res)
945 def test_GET_FILEURL_info_mdmf_extensions(self):
946 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
948 self.failUnlessIn("mutable file (mdmf)", res)
949 self.failUnlessIn(self._quux_txt_uri, res)
950 self.failUnlessIn(self._quux_txt_readonly_uri, res)
954 def test_PUT_overwrite_only_files(self):
955 # create a directory, put a file in that directory.
956 contents, n, filecap = self.makefile(8)
957 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
958 d.addCallback(lambda res:
959 self.PUT(self.public_url + "/foo/dir/file1.txt",
960 self.NEWFILE_CONTENTS))
961 # try to overwrite the file with replace=only-files
963 d.addCallback(lambda res:
964 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
966 d.addCallback(lambda res:
967 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
968 "There was already a child by that name, and you asked me "
970 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
974 def test_PUT_NEWFILEURL(self):
975 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
976 # TODO: we lose the response code, so we can't check this
977 #self.failUnlessReallyEqual(responsecode, 201)
978 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
979 d.addCallback(lambda res:
980 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
981 self.NEWFILE_CONTENTS))
984 def test_PUT_NEWFILEURL_not_mutable(self):
985 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
986 self.NEWFILE_CONTENTS)
987 # TODO: we lose the response code, so we can't check this
988 #self.failUnlessReallyEqual(responsecode, 201)
989 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
990 d.addCallback(lambda res:
991 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
992 self.NEWFILE_CONTENTS))
995 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
996 # this should get us a few segments of an MDMF mutable file,
997 # which we can then test for.
998 contents = self.NEWFILE_CONTENTS * 300000
999 d = self.PUT("/uri?format=mdmf",
1001 def _got_filecap(filecap):
1002 self.failUnless(filecap.startswith("URI:MDMF"))
1004 d.addCallback(_got_filecap)
1005 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1006 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1009 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1010 contents = self.NEWFILE_CONTENTS * 300000
1011 d = self.PUT("/uri?format=sdmf",
1013 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1014 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1017 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1018 contents = self.NEWFILE_CONTENTS * 300000
1019 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1020 400, "Bad Request", "Unknown format: foo",
1021 self.PUT, "/uri?format=foo",
1024 def test_PUT_NEWFILEURL_range_bad(self):
1025 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1026 target = self.public_url + "/foo/new.txt"
1027 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1028 "501 Not Implemented",
1029 "Content-Range in PUT not yet supported",
1030 # (and certainly not for immutable files)
1031 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1033 d.addCallback(lambda res:
1034 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1037 def test_PUT_NEWFILEURL_mutable(self):
1038 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1039 self.NEWFILE_CONTENTS)
1040 # TODO: we lose the response code, so we can't check this
1041 #self.failUnlessReallyEqual(responsecode, 201)
1042 def _check_uri(res):
1043 u = uri.from_string_mutable_filenode(res)
1044 self.failUnless(u.is_mutable())
1045 self.failIf(u.is_readonly())
1047 d.addCallback(_check_uri)
1048 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1049 d.addCallback(lambda res:
1050 self.failUnlessMutableChildContentsAre(self._foo_node,
1052 self.NEWFILE_CONTENTS))
1055 def test_PUT_NEWFILEURL_mutable_toobig(self):
1056 # It is okay to upload large mutable files, so we should be able
1058 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1059 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1062 def test_PUT_NEWFILEURL_replace(self):
1063 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1064 # TODO: we lose the response code, so we can't check this
1065 #self.failUnlessReallyEqual(responsecode, 200)
1066 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1067 d.addCallback(lambda res:
1068 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1069 self.NEWFILE_CONTENTS))
1072 def test_PUT_NEWFILEURL_bad_t(self):
1073 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1074 "PUT to a file: bad t=bogus",
1075 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1079 def test_PUT_NEWFILEURL_no_replace(self):
1080 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1081 self.NEWFILE_CONTENTS)
1082 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1084 "There was already a child by that name, and you asked me "
1085 "to not replace it")
1088 def test_PUT_NEWFILEURL_mkdirs(self):
1089 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1091 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1092 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1093 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1094 d.addCallback(lambda res:
1095 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1096 self.NEWFILE_CONTENTS))
1099 def test_PUT_NEWFILEURL_blocked(self):
1100 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1101 self.NEWFILE_CONTENTS)
1102 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1104 "Unable to create directory 'blockingfile': a file was in the way")
1107 def test_PUT_NEWFILEURL_emptyname(self):
1108 # an empty pathname component (i.e. a double-slash) is disallowed
1109 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1111 "The webapi does not allow empty pathname components",
1112 self.PUT, self.public_url + "/foo//new.txt", "")
1115 def test_DELETE_FILEURL(self):
1116 d = self.DELETE(self.public_url + "/foo/bar.txt")
1117 d.addCallback(lambda res:
1118 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1121 def test_DELETE_FILEURL_missing(self):
1122 d = self.DELETE(self.public_url + "/foo/missing")
1123 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1126 def test_DELETE_FILEURL_missing2(self):
1127 d = self.DELETE(self.public_url + "/missing/missing")
1128 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1131 def failUnlessHasBarDotTxtMetadata(self, res):
1132 data = simplejson.loads(res)
1133 self.failUnless(isinstance(data, list))
1134 self.failUnlessIn("metadata", data[1])
1135 self.failUnlessIn("tahoe", data[1]["metadata"])
1136 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1137 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1138 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1139 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1141 def test_GET_FILEURL_json(self):
1142 # twisted.web.http.parse_qs ignores any query args without an '=', so
1143 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1144 # instead. This may make it tricky to emulate the S3 interface
1146 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1148 self.failUnlessIsBarJSON(data)
1149 self.failUnlessHasBarDotTxtMetadata(data)
1151 d.addCallback(_check1)
1154 def test_GET_FILEURL_json_mutable_type(self):
1155 # The JSON should include format, which says whether the
1156 # file is SDMF or MDMF
1157 d = self.PUT("/uri?format=mdmf",
1158 self.NEWFILE_CONTENTS * 300000)
1159 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1160 def _got_json(json, version):
1161 data = simplejson.loads(json)
1162 assert "filenode" == data[0]
1164 assert isinstance(data, dict)
1166 self.failUnlessIn("format", data)
1167 self.failUnlessEqual(data["format"], version)
1169 d.addCallback(_got_json, "MDMF")
1170 # Now make an SDMF file and check that it is reported correctly.
1171 d.addCallback(lambda ignored:
1172 self.PUT("/uri?format=sdmf",
1173 self.NEWFILE_CONTENTS * 300000))
1174 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1175 d.addCallback(_got_json, "SDMF")
1178 def test_GET_FILEURL_json_mdmf(self):
1179 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1180 d.addCallback(self.failUnlessIsQuuxJSON)
1183 def test_GET_FILEURL_json_missing(self):
1184 d = self.GET(self.public_url + "/foo/missing?json")
1185 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1188 def test_GET_FILEURL_uri(self):
1189 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1191 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1192 d.addCallback(_check)
1193 d.addCallback(lambda res:
1194 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1196 # for now, for files, uris and readonly-uris are the same
1197 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1198 d.addCallback(_check2)
1201 def test_GET_FILEURL_badtype(self):
1202 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1205 self.public_url + "/foo/bar.txt?t=bogus")
1208 def test_CSS_FILE(self):
1209 d = self.GET("/tahoe.css", followRedirect=True)
1211 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1212 self.failUnless(CSS_STYLE.search(res), res)
1213 d.addCallback(_check)
1216 def test_GET_FILEURL_uri_missing(self):
1217 d = self.GET(self.public_url + "/foo/missing?t=uri")
1218 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1221 def _check_upload_and_mkdir_forms(self, html):
1222 # We should have a form to create a file, with radio buttons that allow
1223 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1224 self.failUnlessIn('name="t" value="upload"', html)
1225 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1226 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1227 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1229 # We should also have the ability to create a mutable directory, with
1230 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1231 # or MDMF directory.
1232 self.failUnlessIn('name="t" value="mkdir"', html)
1233 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1234 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1236 self.failUnlessIn(FAVICON_MARKUP, html)
1238 def test_GET_DIRECTORY_html(self):
1239 d = self.GET(self.public_url + "/foo", followRedirect=True)
1241 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1242 self._check_upload_and_mkdir_forms(html)
1243 self.failUnlessIn("quux", html)
1244 d.addCallback(_check)
1247 def test_GET_root_html(self):
1249 d.addCallback(self._check_upload_and_mkdir_forms)
1252 def test_GET_DIRURL(self):
1253 # the addSlash means we get a redirect here
1254 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1256 d = self.GET(self.public_url + "/foo", followRedirect=True)
1258 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1260 # the FILE reference points to a URI, but it should end in bar.txt
1261 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1262 (ROOT, urllib.quote(self._bar_txt_uri)))
1263 get_bar = "".join([r'<td>FILE</td>',
1265 r'<a href="%s">bar.txt</a>' % bar_url,
1267 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1269 self.failUnless(re.search(get_bar, res), res)
1270 for label in ['unlink', 'rename', 'move']:
1271 for line in res.split("\n"):
1272 # find the line that contains the relevant button for bar.txt
1273 if ("form action" in line and
1274 ('value="%s"' % (label,)) in line and
1275 'value="bar.txt"' in line):
1276 # the form target should use a relative URL
1277 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1278 self.failUnlessIn('action="%s"' % foo_url, line)
1279 # and the when_done= should too
1280 #done_url = urllib.quote(???)
1281 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1283 # 'unlink' needs to use POST because it directly has a side effect
1284 if label == 'unlink':
1285 self.failUnlessIn('method="post"', line)
1288 self.fail("unable to find '%s bar.txt' line" % (label,), res)
1290 # the DIR reference just points to a URI
1291 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1292 get_sub = ((r'<td>DIR</td>')
1293 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1294 self.failUnless(re.search(get_sub, res), res)
1295 d.addCallback(_check)
1297 # look at a readonly directory
1298 d.addCallback(lambda res:
1299 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1301 self.failUnlessIn("(read-only)", res)
1302 self.failIfIn("Upload a file", res)
1303 d.addCallback(_check2)
1305 # and at a directory that contains a readonly directory
1306 d.addCallback(lambda res:
1307 self.GET(self.public_url, followRedirect=True))
1309 self.failUnless(re.search('<td>DIR-RO</td>'
1310 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1311 d.addCallback(_check3)
1313 # and an empty directory
1314 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1316 self.failUnlessIn("directory is empty", res)
1317 MKDIR_BUTTON_RE=re.compile('<input type="hidden" name="t" value="mkdir" />.*<legend class="freeform-form-label">Create a new directory in this directory</legend>.*<input type="submit" value="Create" />', re.I)
1318 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1319 d.addCallback(_check4)
1321 # and at a literal directory
1322 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1323 d.addCallback(lambda res:
1324 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1326 self.failUnlessIn('(immutable)', res)
1327 self.failUnless(re.search('<td>FILE</td>'
1328 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1329 d.addCallback(_check5)
1332 def test_GET_DIRURL_badtype(self):
1333 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1337 self.public_url + "/foo?t=bogus")
1340 def test_GET_DIRURL_json(self):
1341 d = self.GET(self.public_url + "/foo?t=json")
1342 d.addCallback(self.failUnlessIsFooJSON)
1345 def test_GET_DIRURL_json_format(self):
1346 d = self.PUT(self.public_url + \
1347 "/foo/sdmf.txt?format=sdmf",
1348 self.NEWFILE_CONTENTS * 300000)
1349 d.addCallback(lambda ignored:
1350 self.PUT(self.public_url + \
1351 "/foo/mdmf.txt?format=mdmf",
1352 self.NEWFILE_CONTENTS * 300000))
1353 # Now we have an MDMF and SDMF file in the directory. If we GET
1354 # its JSON, we should see their encodings.
1355 d.addCallback(lambda ignored:
1356 self.GET(self.public_url + "/foo?t=json"))
1357 def _got_json(json):
1358 data = simplejson.loads(json)
1359 assert data[0] == "dirnode"
1362 kids = data['children']
1364 mdmf_data = kids['mdmf.txt'][1]
1365 self.failUnlessIn("format", mdmf_data)
1366 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1368 sdmf_data = kids['sdmf.txt'][1]
1369 self.failUnlessIn("format", sdmf_data)
1370 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1371 d.addCallback(_got_json)
1375 def test_POST_DIRURL_manifest_no_ophandle(self):
1376 d = self.shouldFail2(error.Error,
1377 "test_POST_DIRURL_manifest_no_ophandle",
1379 "slow operation requires ophandle=",
1380 self.POST, self.public_url, t="start-manifest")
1383 def test_POST_DIRURL_manifest(self):
1384 d = defer.succeed(None)
1385 def getman(ignored, output):
1386 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1387 followRedirect=True)
1388 d.addCallback(self.wait_for_operation, "125")
1389 d.addCallback(self.get_operation_results, "125", output)
1391 d.addCallback(getman, None)
1392 def _got_html(manifest):
1393 self.failUnlessIn("Manifest of SI=", manifest)
1394 self.failUnlessIn("<td>sub</td>", manifest)
1395 self.failUnlessIn(self._sub_uri, manifest)
1396 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1397 self.failUnlessIn(FAVICON_MARKUP, manifest)
1398 d.addCallback(_got_html)
1400 # both t=status and unadorned GET should be identical
1401 d.addCallback(lambda res: self.GET("/operations/125"))
1402 d.addCallback(_got_html)
1404 d.addCallback(getman, "html")
1405 d.addCallback(_got_html)
1406 d.addCallback(getman, "text")
1407 def _got_text(manifest):
1408 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1409 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1410 d.addCallback(_got_text)
1411 d.addCallback(getman, "JSON")
1413 data = res["manifest"]
1415 for (path_list, cap) in data:
1416 got[tuple(path_list)] = cap
1417 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1418 self.failUnlessIn((u"sub", u"baz.txt"), got)
1419 self.failUnlessIn("finished", res)
1420 self.failUnlessIn("origin", res)
1421 self.failUnlessIn("storage-index", res)
1422 self.failUnlessIn("verifycaps", res)
1423 self.failUnlessIn("stats", res)
1424 d.addCallback(_got_json)
1427 def test_POST_DIRURL_deepsize_no_ophandle(self):
1428 d = self.shouldFail2(error.Error,
1429 "test_POST_DIRURL_deepsize_no_ophandle",
1431 "slow operation requires ophandle=",
1432 self.POST, self.public_url, t="start-deep-size")
1435 def test_POST_DIRURL_deepsize(self):
1436 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1437 followRedirect=True)
1438 d.addCallback(self.wait_for_operation, "126")
1439 d.addCallback(self.get_operation_results, "126", "json")
1440 def _got_json(data):
1441 self.failUnlessReallyEqual(data["finished"], True)
1443 self.failUnless(size > 1000)
1444 d.addCallback(_got_json)
1445 d.addCallback(self.get_operation_results, "126", "text")
1447 mo = re.search(r'^size: (\d+)$', res, re.M)
1448 self.failUnless(mo, res)
1449 size = int(mo.group(1))
1450 # with directories, the size varies.
1451 self.failUnless(size > 1000)
1452 d.addCallback(_got_text)
1455 def test_POST_DIRURL_deepstats_no_ophandle(self):
1456 d = self.shouldFail2(error.Error,
1457 "test_POST_DIRURL_deepstats_no_ophandle",
1459 "slow operation requires ophandle=",
1460 self.POST, self.public_url, t="start-deep-stats")
1463 def test_POST_DIRURL_deepstats(self):
1464 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1465 followRedirect=True)
1466 d.addCallback(self.wait_for_operation, "127")
1467 d.addCallback(self.get_operation_results, "127", "json")
1468 def _got_json(stats):
1469 expected = {"count-immutable-files": 3,
1470 "count-mutable-files": 2,
1471 "count-literal-files": 0,
1473 "count-directories": 3,
1474 "size-immutable-files": 57,
1475 "size-literal-files": 0,
1476 #"size-directories": 1912, # varies
1477 #"largest-directory": 1590,
1478 "largest-directory-children": 7,
1479 "largest-immutable-file": 19,
1481 for k,v in expected.iteritems():
1482 self.failUnlessReallyEqual(stats[k], v,
1483 "stats[%s] was %s, not %s" %
1485 self.failUnlessReallyEqual(stats["size-files-histogram"],
1487 d.addCallback(_got_json)
1490 def test_POST_DIRURL_stream_manifest(self):
1491 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1493 self.failUnless(res.endswith("\n"))
1494 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1495 self.failUnlessReallyEqual(len(units), 9)
1496 self.failUnlessEqual(units[-1]["type"], "stats")
1498 self.failUnlessEqual(first["path"], [])
1499 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1500 self.failUnlessEqual(first["type"], "directory")
1501 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1502 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1503 self.failIfEqual(baz["storage-index"], None)
1504 self.failIfEqual(baz["verifycap"], None)
1505 self.failIfEqual(baz["repaircap"], None)
1506 # XXX: Add quux and baz to this test.
1508 d.addCallback(_check)
1511 def test_GET_DIRURL_uri(self):
1512 d = self.GET(self.public_url + "/foo?t=uri")
1514 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1515 d.addCallback(_check)
1518 def test_GET_DIRURL_readonly_uri(self):
1519 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1521 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1522 d.addCallback(_check)
1525 def test_PUT_NEWDIRURL(self):
1526 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1527 d.addCallback(lambda res:
1528 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1529 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1530 d.addCallback(self.failUnlessNodeKeysAre, [])
1533 def test_PUT_NEWDIRURL_mdmf(self):
1534 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1535 d.addCallback(lambda res:
1536 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1537 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1538 d.addCallback(lambda node:
1539 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1542 def test_PUT_NEWDIRURL_sdmf(self):
1543 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1545 d.addCallback(lambda res:
1546 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1547 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1548 d.addCallback(lambda node:
1549 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1552 def test_PUT_NEWDIRURL_bad_format(self):
1553 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1554 400, "Bad Request", "Unknown format: foo",
1555 self.PUT, self.public_url +
1556 "/foo/newdir=?t=mkdir&format=foo", "")
1558 def test_POST_NEWDIRURL(self):
1559 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1560 d.addCallback(lambda res:
1561 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1562 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1563 d.addCallback(self.failUnlessNodeKeysAre, [])
1566 def test_POST_NEWDIRURL_mdmf(self):
1567 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1568 d.addCallback(lambda res:
1569 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1570 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1571 d.addCallback(lambda node:
1572 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1575 def test_POST_NEWDIRURL_sdmf(self):
1576 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1577 d.addCallback(lambda res:
1578 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1579 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1580 d.addCallback(lambda node:
1581 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1584 def test_POST_NEWDIRURL_bad_format(self):
1585 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1586 400, "Bad Request", "Unknown format: foo",
1587 self.POST2, self.public_url + \
1588 "/foo/newdir?t=mkdir&format=foo", "")
1590 def test_POST_NEWDIRURL_emptyname(self):
1591 # an empty pathname component (i.e. a double-slash) is disallowed
1592 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1594 "The webapi does not allow empty pathname components, i.e. a double slash",
1595 self.POST, self.public_url + "//?t=mkdir")
1598 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1599 (newkids, caps) = self._create_initial_children()
1600 query = "/foo/newdir?t=mkdir-with-children"
1601 if version == MDMF_VERSION:
1602 query += "&format=mdmf"
1603 elif version == SDMF_VERSION:
1604 query += "&format=sdmf"
1606 version = SDMF_VERSION # for later
1607 d = self.POST2(self.public_url + query,
1608 simplejson.dumps(newkids))
1610 n = self.s.create_node_from_uri(uri.strip())
1611 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1612 self.failUnlessEqual(n._node.get_version(), version)
1613 d2.addCallback(lambda ign:
1614 self.failUnlessROChildURIIs(n, u"child-imm",
1616 d2.addCallback(lambda ign:
1617 self.failUnlessRWChildURIIs(n, u"child-mutable",
1619 d2.addCallback(lambda ign:
1620 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1622 d2.addCallback(lambda ign:
1623 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1624 caps['unknown_rocap']))
1625 d2.addCallback(lambda ign:
1626 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1627 caps['unknown_rwcap']))
1628 d2.addCallback(lambda ign:
1629 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1630 caps['unknown_immcap']))
1631 d2.addCallback(lambda ign:
1632 self.failUnlessRWChildURIIs(n, u"dirchild",
1634 d2.addCallback(lambda ign:
1635 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1637 d2.addCallback(lambda ign:
1638 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1639 caps['emptydircap']))
1641 d.addCallback(_check)
1642 d.addCallback(lambda res:
1643 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1644 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1645 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1646 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1647 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1650 def test_POST_NEWDIRURL_initial_children(self):
1651 return self._do_POST_NEWDIRURL_initial_children_test()
1653 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1654 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1656 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1657 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1659 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1660 (newkids, caps) = self._create_initial_children()
1661 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1662 400, "Bad Request", "Unknown format: foo",
1663 self.POST2, self.public_url + \
1664 "/foo/newdir?t=mkdir-with-children&format=foo",
1665 simplejson.dumps(newkids))
1667 def test_POST_NEWDIRURL_immutable(self):
1668 (newkids, caps) = self._create_immutable_children()
1669 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1670 simplejson.dumps(newkids))
1672 n = self.s.create_node_from_uri(uri.strip())
1673 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1674 d2.addCallback(lambda ign:
1675 self.failUnlessROChildURIIs(n, u"child-imm",
1677 d2.addCallback(lambda ign:
1678 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1679 caps['unknown_immcap']))
1680 d2.addCallback(lambda ign:
1681 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1683 d2.addCallback(lambda ign:
1684 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1686 d2.addCallback(lambda ign:
1687 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1688 caps['emptydircap']))
1690 d.addCallback(_check)
1691 d.addCallback(lambda res:
1692 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1693 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1694 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1695 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1696 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1697 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1698 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1699 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1700 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1701 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1702 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1703 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1704 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1705 d.addErrback(self.explain_web_error)
1708 def test_POST_NEWDIRURL_immutable_bad(self):
1709 (newkids, caps) = self._create_initial_children()
1710 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1712 "needed to be immutable but was not",
1714 self.public_url + "/foo/newdir?t=mkdir-immutable",
1715 simplejson.dumps(newkids))
1718 def test_PUT_NEWDIRURL_exists(self):
1719 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1720 d.addCallback(lambda res:
1721 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1722 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1723 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1726 def test_PUT_NEWDIRURL_blocked(self):
1727 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1728 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1730 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1731 d.addCallback(lambda res:
1732 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1733 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1734 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1737 def test_PUT_NEWDIRURL_mkdir_p(self):
1738 d = defer.succeed(None)
1739 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1740 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1741 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1742 def mkdir_p(mkpnode):
1743 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1745 def made_subsub(ssuri):
1746 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1747 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1749 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1751 d.addCallback(made_subsub)
1753 d.addCallback(mkdir_p)
1756 def test_PUT_NEWDIRURL_mkdirs(self):
1757 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1758 d.addCallback(lambda res:
1759 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1760 d.addCallback(lambda res:
1761 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1762 d.addCallback(lambda res:
1763 self._foo_node.get_child_at_path(u"subdir/newdir"))
1764 d.addCallback(self.failUnlessNodeKeysAre, [])
1767 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1768 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1769 d.addCallback(lambda ignored:
1770 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1771 d.addCallback(lambda ignored:
1772 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1773 d.addCallback(lambda ignored:
1774 self._foo_node.get_child_at_path(u"subdir"))
1775 def _got_subdir(subdir):
1776 # XXX: What we want?
1777 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1778 self.failUnlessNodeHasChild(subdir, u"newdir")
1779 return subdir.get_child_at_path(u"newdir")
1780 d.addCallback(_got_subdir)
1781 d.addCallback(lambda newdir:
1782 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1785 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1786 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1787 d.addCallback(lambda ignored:
1788 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1789 d.addCallback(lambda ignored:
1790 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1791 d.addCallback(lambda ignored:
1792 self._foo_node.get_child_at_path(u"subdir"))
1793 def _got_subdir(subdir):
1794 # XXX: What we want?
1795 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1796 self.failUnlessNodeHasChild(subdir, u"newdir")
1797 return subdir.get_child_at_path(u"newdir")
1798 d.addCallback(_got_subdir)
1799 d.addCallback(lambda newdir:
1800 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1803 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1804 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1805 400, "Bad Request", "Unknown format: foo",
1806 self.PUT, self.public_url + \
1807 "/foo/subdir/newdir?t=mkdir&format=foo",
1810 def test_DELETE_DIRURL(self):
1811 d = self.DELETE(self.public_url + "/foo")
1812 d.addCallback(lambda res:
1813 self.failIfNodeHasChild(self.public_root, u"foo"))
1816 def test_DELETE_DIRURL_missing(self):
1817 d = self.DELETE(self.public_url + "/foo/missing")
1818 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1819 d.addCallback(lambda res:
1820 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1823 def test_DELETE_DIRURL_missing2(self):
1824 d = self.DELETE(self.public_url + "/missing")
1825 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1828 def dump_root(self):
1830 w = webish.DirnodeWalkerMixin()
1831 def visitor(childpath, childnode, metadata):
1833 d = w.walk(self.public_root, visitor)
1836 def failUnlessNodeKeysAre(self, node, expected_keys):
1837 for k in expected_keys:
1838 assert isinstance(k, unicode)
1840 def _check(children):
1841 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1842 d.addCallback(_check)
1844 def failUnlessNodeHasChild(self, node, name):
1845 assert isinstance(name, unicode)
1847 def _check(children):
1848 self.failUnlessIn(name, children)
1849 d.addCallback(_check)
1851 def failIfNodeHasChild(self, node, name):
1852 assert isinstance(name, unicode)
1854 def _check(children):
1855 self.failIfIn(name, children)
1856 d.addCallback(_check)
1859 def failUnlessChildContentsAre(self, node, name, expected_contents):
1860 assert isinstance(name, unicode)
1861 d = node.get_child_at_path(name)
1862 d.addCallback(lambda node: download_to_data(node))
1863 def _check(contents):
1864 self.failUnlessReallyEqual(contents, expected_contents)
1865 d.addCallback(_check)
1868 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1869 assert isinstance(name, unicode)
1870 d = node.get_child_at_path(name)
1871 d.addCallback(lambda node: node.download_best_version())
1872 def _check(contents):
1873 self.failUnlessReallyEqual(contents, expected_contents)
1874 d.addCallback(_check)
1877 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1878 assert isinstance(name, unicode)
1879 d = node.get_child_at_path(name)
1881 self.failUnless(child.is_unknown() or not child.is_readonly())
1882 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1883 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1884 expected_ro_uri = self._make_readonly(expected_uri)
1886 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1887 d.addCallback(_check)
1890 def failUnlessROChildURIIs(self, node, name, expected_uri):
1891 assert isinstance(name, unicode)
1892 d = node.get_child_at_path(name)
1894 self.failUnless(child.is_unknown() or child.is_readonly())
1895 self.failUnlessReallyEqual(child.get_write_uri(), None)
1896 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1897 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1898 d.addCallback(_check)
1901 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1902 assert isinstance(name, unicode)
1903 d = node.get_child_at_path(name)
1905 self.failUnless(child.is_unknown() or not child.is_readonly())
1906 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1907 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1908 expected_ro_uri = self._make_readonly(got_uri)
1910 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1911 d.addCallback(_check)
1914 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1915 assert isinstance(name, unicode)
1916 d = node.get_child_at_path(name)
1918 self.failUnless(child.is_unknown() or child.is_readonly())
1919 self.failUnlessReallyEqual(child.get_write_uri(), None)
1920 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1921 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1922 d.addCallback(_check)
1925 def failUnlessCHKURIHasContents(self, got_uri, contents):
1926 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1928 def test_POST_upload(self):
1929 d = self.POST(self.public_url + "/foo", t="upload",
1930 file=("new.txt", self.NEWFILE_CONTENTS))
1932 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1933 d.addCallback(lambda res:
1934 self.failUnlessChildContentsAre(fn, u"new.txt",
1935 self.NEWFILE_CONTENTS))
1938 def test_POST_upload_unicode(self):
1939 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1940 d = self.POST(self.public_url + "/foo", t="upload",
1941 file=(filename, self.NEWFILE_CONTENTS))
1943 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1944 d.addCallback(lambda res:
1945 self.failUnlessChildContentsAre(fn, filename,
1946 self.NEWFILE_CONTENTS))
1947 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1948 d.addCallback(lambda res: self.GET(target_url))
1949 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1950 self.NEWFILE_CONTENTS,
1954 def test_POST_upload_unicode_named(self):
1955 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1956 d = self.POST(self.public_url + "/foo", t="upload",
1958 file=("overridden", self.NEWFILE_CONTENTS))
1960 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1961 d.addCallback(lambda res:
1962 self.failUnlessChildContentsAre(fn, filename,
1963 self.NEWFILE_CONTENTS))
1964 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1965 d.addCallback(lambda res: self.GET(target_url))
1966 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1967 self.NEWFILE_CONTENTS,
1971 def test_POST_upload_no_link(self):
1972 d = self.POST("/uri", t="upload",
1973 file=("new.txt", self.NEWFILE_CONTENTS))
1974 def _check_upload_results(page):
1975 # this should be a page which describes the results of the upload
1976 # that just finished.
1977 self.failUnlessIn("Upload Results:", page)
1978 self.failUnlessIn("URI:", page)
1979 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1980 mo = uri_re.search(page)
1981 self.failUnless(mo, page)
1982 new_uri = mo.group(1)
1984 d.addCallback(_check_upload_results)
1985 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1988 def test_POST_upload_no_link_whendone(self):
1989 d = self.POST("/uri", t="upload", when_done="/",
1990 file=("new.txt", self.NEWFILE_CONTENTS))
1991 d.addBoth(self.shouldRedirect, "/")
1994 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1995 d = defer.maybeDeferred(callable, *args, **kwargs)
1997 if isinstance(res, failure.Failure):
1998 res.trap(error.PageRedirect)
1999 statuscode = res.value.status
2000 target = res.value.location
2001 return checker(statuscode, target)
2002 self.fail("%s: callable was supposed to redirect, not return '%s'"
2007 def test_POST_upload_no_link_whendone_results(self):
2008 def check(statuscode, target):
2009 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2010 self.failUnless(target.startswith(self.webish_url), target)
2011 return client.getPage(target, method="GET")
2012 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2014 self.POST, "/uri", t="upload",
2015 when_done="/uri/%(uri)s",
2016 file=("new.txt", self.NEWFILE_CONTENTS))
2017 d.addCallback(lambda res:
2018 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2021 def test_POST_upload_no_link_mutable(self):
2022 d = self.POST("/uri", t="upload", mutable="true",
2023 file=("new.txt", self.NEWFILE_CONTENTS))
2024 def _check(filecap):
2025 filecap = filecap.strip()
2026 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2027 self.filecap = filecap
2028 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2029 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
2030 n = self.s.create_node_from_uri(filecap)
2031 return n.download_best_version()
2032 d.addCallback(_check)
2034 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2035 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2036 d.addCallback(_check2)
2038 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2039 return self.GET("/file/%s" % urllib.quote(self.filecap))
2040 d.addCallback(_check3)
2042 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2043 d.addCallback(_check4)
2046 def test_POST_upload_no_link_mutable_toobig(self):
2047 # The SDMF size limit is no longer in place, so we should be
2048 # able to upload mutable files that are as large as we want them
2050 d = self.POST("/uri", t="upload", mutable="true",
2051 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2055 def test_POST_upload_format_unlinked(self):
2056 def _check_upload_unlinked(ign, format, uri_prefix):
2057 filename = format + ".txt"
2058 d = self.POST("/uri?t=upload&format=" + format,
2059 file=(filename, self.NEWFILE_CONTENTS * 300000))
2060 def _got_results(results):
2061 if format.upper() in ("SDMF", "MDMF"):
2062 # webapi.rst says this returns a filecap
2065 # for immutable, it returns an "upload results page", and
2066 # the filecap is buried inside
2067 line = [l for l in results.split("\n") if "URI: " in l][0]
2068 mo = re.search(r'<span>([^<]+)</span>', line)
2069 filecap = mo.group(1)
2070 self.failUnless(filecap.startswith(uri_prefix),
2071 (uri_prefix, filecap))
2072 return self.GET("/uri/%s?t=json" % filecap)
2073 d.addCallback(_got_results)
2074 def _got_json(json):
2075 data = simplejson.loads(json)
2077 self.failUnlessIn("format", data)
2078 self.failUnlessEqual(data["format"], format.upper())
2079 d.addCallback(_got_json)
2081 d = defer.succeed(None)
2082 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2083 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2084 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2085 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2088 def test_POST_upload_bad_format_unlinked(self):
2089 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2090 400, "Bad Request", "Unknown format: foo",
2092 "/uri?t=upload&format=foo",
2093 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2095 def test_POST_upload_format(self):
2096 def _check_upload(ign, format, uri_prefix, fn=None):
2097 filename = format + ".txt"
2098 d = self.POST(self.public_url +
2099 "/foo?t=upload&format=" + format,
2100 file=(filename, self.NEWFILE_CONTENTS * 300000))
2101 def _got_filecap(filecap):
2103 filenameu = unicode(filename)
2104 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2105 self.failUnless(filecap.startswith(uri_prefix))
2106 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2107 d.addCallback(_got_filecap)
2108 def _got_json(json):
2109 data = simplejson.loads(json)
2111 self.failUnlessIn("format", data)
2112 self.failUnlessEqual(data["format"], format.upper())
2113 d.addCallback(_got_json)
2116 d = defer.succeed(None)
2117 d.addCallback(_check_upload, "chk", "URI:CHK")
2118 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2119 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2120 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2123 def test_POST_upload_bad_format(self):
2124 return self.shouldHTTPError("POST_upload_bad_format",
2125 400, "Bad Request", "Unknown format: foo",
2126 self.POST, self.public_url + \
2127 "/foo?t=upload&format=foo",
2128 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2130 def test_POST_upload_mutable(self):
2131 # this creates a mutable file
2132 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2133 file=("new.txt", self.NEWFILE_CONTENTS))
2135 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2136 d.addCallback(lambda res:
2137 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2138 self.NEWFILE_CONTENTS))
2139 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2141 self.failUnless(IMutableFileNode.providedBy(newnode))
2142 self.failUnless(newnode.is_mutable())
2143 self.failIf(newnode.is_readonly())
2144 self._mutable_node = newnode
2145 self._mutable_uri = newnode.get_uri()
2148 # now upload it again and make sure that the URI doesn't change
2149 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2150 d.addCallback(lambda res:
2151 self.POST(self.public_url + "/foo", t="upload",
2153 file=("new.txt", NEWER_CONTENTS)))
2154 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2155 d.addCallback(lambda res:
2156 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2158 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2160 self.failUnless(IMutableFileNode.providedBy(newnode))
2161 self.failUnless(newnode.is_mutable())
2162 self.failIf(newnode.is_readonly())
2163 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2164 d.addCallback(_got2)
2166 # upload a second time, using PUT instead of POST
2167 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2168 d.addCallback(lambda res:
2169 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2170 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2171 d.addCallback(lambda res:
2172 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2175 # finally list the directory, since mutable files are displayed
2176 # slightly differently
2178 d.addCallback(lambda res:
2179 self.GET(self.public_url + "/foo/",
2180 followRedirect=True))
2181 def _check_page(res):
2182 # TODO: assert more about the contents
2183 self.failUnlessIn("SSK", res)
2185 d.addCallback(_check_page)
2187 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2189 self.failUnless(IMutableFileNode.providedBy(newnode))
2190 self.failUnless(newnode.is_mutable())
2191 self.failIf(newnode.is_readonly())
2192 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2193 d.addCallback(_got3)
2195 # look at the JSON form of the enclosing directory
2196 d.addCallback(lambda res:
2197 self.GET(self.public_url + "/foo/?t=json",
2198 followRedirect=True))
2199 def _check_page_json(res):
2200 parsed = simplejson.loads(res)
2201 self.failUnlessEqual(parsed[0], "dirnode")
2202 children = dict( [(unicode(name),value)
2204 in parsed[1]["children"].iteritems()] )
2205 self.failUnlessIn(u"new.txt", children)
2206 new_json = children[u"new.txt"]
2207 self.failUnlessEqual(new_json[0], "filenode")
2208 self.failUnless(new_json[1]["mutable"])
2209 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2210 ro_uri = self._mutable_node.get_readonly().to_string()
2211 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2212 d.addCallback(_check_page_json)
2214 # and the JSON form of the file
2215 d.addCallback(lambda res:
2216 self.GET(self.public_url + "/foo/new.txt?t=json"))
2217 def _check_file_json(res):
2218 parsed = simplejson.loads(res)
2219 self.failUnlessEqual(parsed[0], "filenode")
2220 self.failUnless(parsed[1]["mutable"])
2221 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2222 ro_uri = self._mutable_node.get_readonly().to_string()
2223 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2224 d.addCallback(_check_file_json)
2226 # and look at t=uri and t=readonly-uri
2227 d.addCallback(lambda res:
2228 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2229 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2230 d.addCallback(lambda res:
2231 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2232 def _check_ro_uri(res):
2233 ro_uri = self._mutable_node.get_readonly().to_string()
2234 self.failUnlessReallyEqual(res, ro_uri)
2235 d.addCallback(_check_ro_uri)
2237 # make sure we can get to it from /uri/URI
2238 d.addCallback(lambda res:
2239 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2240 d.addCallback(lambda res:
2241 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2243 # and that HEAD computes the size correctly
2244 d.addCallback(lambda res:
2245 self.HEAD(self.public_url + "/foo/new.txt",
2246 return_response=True))
2247 def _got_headers((res, status, headers)):
2248 self.failUnlessReallyEqual(res, "")
2249 self.failUnlessReallyEqual(headers["content-length"][0],
2250 str(len(NEW2_CONTENTS)))
2251 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2252 d.addCallback(_got_headers)
2254 # make sure that outdated size limits aren't enforced anymore.
2255 d.addCallback(lambda ignored:
2256 self.POST(self.public_url + "/foo", t="upload",
2259 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2260 d.addErrback(self.dump_error)
2263 def test_POST_upload_mutable_toobig(self):
2264 # SDMF had a size limti that was removed a while ago. MDMF has
2265 # never had a size limit. Test to make sure that we do not
2266 # encounter errors when trying to upload large mutable files,
2267 # since there should be no coded prohibitions regarding large
2269 d = self.POST(self.public_url + "/foo",
2270 t="upload", mutable="true",
2271 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2274 def dump_error(self, f):
2275 # if the web server returns an error code (like 400 Bad Request),
2276 # web.client.getPage puts the HTTP response body into the .response
2277 # attribute of the exception object that it gives back. It does not
2278 # appear in the Failure's repr(), so the ERROR that trial displays
2279 # will be rather terse and unhelpful. addErrback this method to the
2280 # end of your chain to get more information out of these errors.
2281 if f.check(error.Error):
2282 print "web.error.Error:"
2284 print f.value.response
2287 def test_POST_upload_replace(self):
2288 d = self.POST(self.public_url + "/foo", t="upload",
2289 file=("bar.txt", self.NEWFILE_CONTENTS))
2291 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2292 d.addCallback(lambda res:
2293 self.failUnlessChildContentsAre(fn, u"bar.txt",
2294 self.NEWFILE_CONTENTS))
2297 def test_POST_upload_no_replace_ok(self):
2298 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2299 file=("new.txt", self.NEWFILE_CONTENTS))
2300 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2301 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2302 self.NEWFILE_CONTENTS))
2305 def test_POST_upload_no_replace_queryarg(self):
2306 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2307 file=("bar.txt", self.NEWFILE_CONTENTS))
2308 d.addBoth(self.shouldFail, error.Error,
2309 "POST_upload_no_replace_queryarg",
2311 "There was already a child by that name, and you asked me "
2312 "to not replace it")
2313 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2314 d.addCallback(self.failUnlessIsBarDotTxt)
2317 def test_POST_upload_no_replace_field(self):
2318 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2319 file=("bar.txt", self.NEWFILE_CONTENTS))
2320 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2322 "There was already a child by that name, and you asked me "
2323 "to not replace it")
2324 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2325 d.addCallback(self.failUnlessIsBarDotTxt)
2328 def test_POST_upload_whendone(self):
2329 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2330 file=("new.txt", self.NEWFILE_CONTENTS))
2331 d.addBoth(self.shouldRedirect, "/THERE")
2333 d.addCallback(lambda res:
2334 self.failUnlessChildContentsAre(fn, u"new.txt",
2335 self.NEWFILE_CONTENTS))
2338 def test_POST_upload_named(self):
2340 d = self.POST(self.public_url + "/foo", t="upload",
2341 name="new.txt", file=self.NEWFILE_CONTENTS)
2342 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2343 d.addCallback(lambda res:
2344 self.failUnlessChildContentsAre(fn, u"new.txt",
2345 self.NEWFILE_CONTENTS))
2348 def test_POST_upload_named_badfilename(self):
2349 d = self.POST(self.public_url + "/foo", t="upload",
2350 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2351 d.addBoth(self.shouldFail, error.Error,
2352 "test_POST_upload_named_badfilename",
2354 "name= may not contain a slash",
2356 # make sure that nothing was added
2357 d.addCallback(lambda res:
2358 self.failUnlessNodeKeysAre(self._foo_node,
2359 [u"bar.txt", u"baz.txt", u"blockingfile",
2360 u"empty", u"n\u00fc.txt", u"quux.txt",
2364 def test_POST_FILEURL_check(self):
2365 bar_url = self.public_url + "/foo/bar.txt"
2366 d = self.POST(bar_url, t="check")
2368 self.failUnlessIn("Healthy :", res)
2369 d.addCallback(_check)
2370 redir_url = "http://allmydata.org/TARGET"
2371 def _check2(statuscode, target):
2372 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2373 self.failUnlessReallyEqual(target, redir_url)
2374 d.addCallback(lambda res:
2375 self.shouldRedirect2("test_POST_FILEURL_check",
2379 when_done=redir_url))
2380 d.addCallback(lambda res:
2381 self.POST(bar_url, t="check", return_to=redir_url))
2383 self.failUnlessIn("Healthy :", res)
2384 self.failUnlessIn("Return to file", res)
2385 self.failUnlessIn(redir_url, res)
2386 d.addCallback(_check3)
2388 d.addCallback(lambda res:
2389 self.POST(bar_url, t="check", output="JSON"))
2390 def _check_json(res):
2391 data = simplejson.loads(res)
2392 self.failUnlessIn("storage-index", data)
2393 self.failUnless(data["results"]["healthy"])
2394 d.addCallback(_check_json)
2398 def test_POST_FILEURL_check_and_repair(self):
2399 bar_url = self.public_url + "/foo/bar.txt"
2400 d = self.POST(bar_url, t="check", repair="true")
2402 self.failUnlessIn("Healthy :", res)
2403 d.addCallback(_check)
2404 redir_url = "http://allmydata.org/TARGET"
2405 def _check2(statuscode, target):
2406 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2407 self.failUnlessReallyEqual(target, redir_url)
2408 d.addCallback(lambda res:
2409 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2412 t="check", repair="true",
2413 when_done=redir_url))
2414 d.addCallback(lambda res:
2415 self.POST(bar_url, t="check", return_to=redir_url))
2417 self.failUnlessIn("Healthy :", res)
2418 self.failUnlessIn("Return to file", res)
2419 self.failUnlessIn(redir_url, res)
2420 d.addCallback(_check3)
2423 def test_POST_DIRURL_check(self):
2424 foo_url = self.public_url + "/foo/"
2425 d = self.POST(foo_url, t="check")
2427 self.failUnlessIn("Healthy :", res)
2428 d.addCallback(_check)
2429 redir_url = "http://allmydata.org/TARGET"
2430 def _check2(statuscode, target):
2431 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2432 self.failUnlessReallyEqual(target, redir_url)
2433 d.addCallback(lambda res:
2434 self.shouldRedirect2("test_POST_DIRURL_check",
2438 when_done=redir_url))
2439 d.addCallback(lambda res:
2440 self.POST(foo_url, t="check", return_to=redir_url))
2442 self.failUnlessIn("Healthy :", res)
2443 self.failUnlessIn("Return to file/directory", res)
2444 self.failUnlessIn(redir_url, res)
2445 d.addCallback(_check3)
2447 d.addCallback(lambda res:
2448 self.POST(foo_url, t="check", output="JSON"))
2449 def _check_json(res):
2450 data = simplejson.loads(res)
2451 self.failUnlessIn("storage-index", data)
2452 self.failUnless(data["results"]["healthy"])
2453 d.addCallback(_check_json)
2457 def test_POST_DIRURL_check_and_repair(self):
2458 foo_url = self.public_url + "/foo/"
2459 d = self.POST(foo_url, t="check", repair="true")
2461 self.failUnlessIn("Healthy :", res)
2462 d.addCallback(_check)
2463 redir_url = "http://allmydata.org/TARGET"
2464 def _check2(statuscode, target):
2465 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2466 self.failUnlessReallyEqual(target, redir_url)
2467 d.addCallback(lambda res:
2468 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2471 t="check", repair="true",
2472 when_done=redir_url))
2473 d.addCallback(lambda res:
2474 self.POST(foo_url, t="check", return_to=redir_url))
2476 self.failUnlessIn("Healthy :", res)
2477 self.failUnlessIn("Return to file/directory", res)
2478 self.failUnlessIn(redir_url, res)
2479 d.addCallback(_check3)
2482 def test_POST_FILEURL_mdmf_check(self):
2483 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2484 d = self.POST(quux_url, t="check")
2486 self.failUnlessIn("Healthy", res)
2487 d.addCallback(_check)
2488 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2489 d.addCallback(lambda ignored:
2490 self.POST(quux_extension_url, t="check"))
2491 d.addCallback(_check)
2494 def test_POST_FILEURL_mdmf_check_and_repair(self):
2495 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2496 d = self.POST(quux_url, t="check", repair="true")
2498 self.failUnlessIn("Healthy", res)
2499 d.addCallback(_check)
2500 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2501 d.addCallback(lambda ignored:
2502 self.POST(quux_extension_url, t="check", repair="true"))
2503 d.addCallback(_check)
2506 def wait_for_operation(self, ignored, ophandle):
2507 url = "/operations/" + ophandle
2508 url += "?t=status&output=JSON"
2511 data = simplejson.loads(res)
2512 if not data["finished"]:
2513 d = self.stall(delay=1.0)
2514 d.addCallback(self.wait_for_operation, ophandle)
2520 def get_operation_results(self, ignored, ophandle, output=None):
2521 url = "/operations/" + ophandle
2524 url += "&output=" + output
2527 if output and output.lower() == "json":
2528 return simplejson.loads(res)
2533 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2534 d = self.shouldFail2(error.Error,
2535 "test_POST_DIRURL_deepcheck_no_ophandle",
2537 "slow operation requires ophandle=",
2538 self.POST, self.public_url, t="start-deep-check")
2541 def test_POST_DIRURL_deepcheck(self):
2542 def _check_redirect(statuscode, target):
2543 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2544 self.failUnless(target.endswith("/operations/123"))
2545 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2546 self.POST, self.public_url,
2547 t="start-deep-check", ophandle="123")
2548 d.addCallback(self.wait_for_operation, "123")
2549 def _check_json(data):
2550 self.failUnlessReallyEqual(data["finished"], True)
2551 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2552 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2553 d.addCallback(_check_json)
2554 d.addCallback(self.get_operation_results, "123", "html")
2555 def _check_html(res):
2556 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2557 self.failUnlessIn("Objects Healthy: <span>10</span>", res)
2558 self.failUnlessIn(FAVICON_MARKUP, res)
2559 d.addCallback(_check_html)
2561 d.addCallback(lambda res:
2562 self.GET("/operations/123/"))
2563 d.addCallback(_check_html) # should be the same as without the slash
2565 d.addCallback(lambda res:
2566 self.shouldFail2(error.Error, "one", "404 Not Found",
2567 "No detailed results for SI bogus",
2568 self.GET, "/operations/123/bogus"))
2570 foo_si = self._foo_node.get_storage_index()
2571 foo_si_s = base32.b2a(foo_si)
2572 d.addCallback(lambda res:
2573 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2574 def _check_foo_json(res):
2575 data = simplejson.loads(res)
2576 self.failUnlessEqual(data["storage-index"], foo_si_s)
2577 self.failUnless(data["results"]["healthy"])
2578 d.addCallback(_check_foo_json)
2581 def test_POST_DIRURL_deepcheck_and_repair(self):
2582 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2583 ophandle="124", output="json", followRedirect=True)
2584 d.addCallback(self.wait_for_operation, "124")
2585 def _check_json(data):
2586 self.failUnlessReallyEqual(data["finished"], True)
2587 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2588 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2589 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2590 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2591 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2592 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2593 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2594 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2595 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2596 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2597 d.addCallback(_check_json)
2598 d.addCallback(self.get_operation_results, "124", "html")
2599 def _check_html(res):
2600 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2602 self.failUnlessIn("Objects Healthy (before repair): <span>10</span>", res)
2603 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2604 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2606 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2607 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2608 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2610 self.failUnlessIn("Objects Healthy (after repair): <span>10</span>", res)
2611 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2612 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2614 self.failUnlessIn(FAVICON_MARKUP, res)
2615 d.addCallback(_check_html)
2618 def test_POST_FILEURL_bad_t(self):
2619 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2620 "POST to file: bad t=bogus",
2621 self.POST, self.public_url + "/foo/bar.txt",
2625 def test_POST_mkdir(self): # return value?
2626 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2627 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2628 d.addCallback(self.failUnlessNodeKeysAre, [])
2631 def test_POST_mkdir_mdmf(self):
2632 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2633 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2634 d.addCallback(lambda node:
2635 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2638 def test_POST_mkdir_sdmf(self):
2639 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2640 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2641 d.addCallback(lambda node:
2642 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2645 def test_POST_mkdir_bad_format(self):
2646 return self.shouldHTTPError("POST_mkdir_bad_format",
2647 400, "Bad Request", "Unknown format: foo",
2648 self.POST, self.public_url +
2649 "/foo?t=mkdir&name=newdir&format=foo")
2651 def test_POST_mkdir_initial_children(self):
2652 (newkids, caps) = self._create_initial_children()
2653 d = self.POST2(self.public_url +
2654 "/foo?t=mkdir-with-children&name=newdir",
2655 simplejson.dumps(newkids))
2656 d.addCallback(lambda res:
2657 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2658 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2659 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2660 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2661 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2664 def test_POST_mkdir_initial_children_mdmf(self):
2665 (newkids, caps) = self._create_initial_children()
2666 d = self.POST2(self.public_url +
2667 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2668 simplejson.dumps(newkids))
2669 d.addCallback(lambda res:
2670 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2671 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2672 d.addCallback(lambda node:
2673 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2674 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2675 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2680 def test_POST_mkdir_initial_children_sdmf(self):
2681 (newkids, caps) = self._create_initial_children()
2682 d = self.POST2(self.public_url +
2683 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2684 simplejson.dumps(newkids))
2685 d.addCallback(lambda res:
2686 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2687 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2688 d.addCallback(lambda node:
2689 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2690 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2691 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2695 def test_POST_mkdir_initial_children_bad_format(self):
2696 (newkids, caps) = self._create_initial_children()
2697 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2698 400, "Bad Request", "Unknown format: foo",
2699 self.POST, self.public_url + \
2700 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2701 simplejson.dumps(newkids))
2703 def test_POST_mkdir_immutable(self):
2704 (newkids, caps) = self._create_immutable_children()
2705 d = self.POST2(self.public_url +
2706 "/foo?t=mkdir-immutable&name=newdir",
2707 simplejson.dumps(newkids))
2708 d.addCallback(lambda res:
2709 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2710 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2711 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2712 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2713 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2714 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2715 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2716 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2717 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2718 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2719 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2720 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2721 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2724 def test_POST_mkdir_immutable_bad(self):
2725 (newkids, caps) = self._create_initial_children()
2726 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2728 "needed to be immutable but was not",
2731 "/foo?t=mkdir-immutable&name=newdir",
2732 simplejson.dumps(newkids))
2735 def test_POST_mkdir_2(self):
2736 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2737 d.addCallback(lambda res:
2738 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2739 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2740 d.addCallback(self.failUnlessNodeKeysAre, [])
2743 def test_POST_mkdirs_2(self):
2744 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2745 d.addCallback(lambda res:
2746 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2747 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2748 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2749 d.addCallback(self.failUnlessNodeKeysAre, [])
2752 def test_POST_mkdir_no_parentdir_noredirect(self):
2753 d = self.POST("/uri?t=mkdir")
2754 def _after_mkdir(res):
2755 uri.DirectoryURI.init_from_string(res)
2756 d.addCallback(_after_mkdir)
2759 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2760 d = self.POST("/uri?t=mkdir&format=mdmf")
2761 def _after_mkdir(res):
2762 u = uri.from_string(res)
2763 # Check that this is an MDMF writecap
2764 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2765 d.addCallback(_after_mkdir)
2768 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2769 d = self.POST("/uri?t=mkdir&format=sdmf")
2770 def _after_mkdir(res):
2771 u = uri.from_string(res)
2772 self.failUnlessIsInstance(u, uri.DirectoryURI)
2773 d.addCallback(_after_mkdir)
2776 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2777 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2778 400, "Bad Request", "Unknown format: foo",
2779 self.POST, self.public_url +
2780 "/uri?t=mkdir&format=foo")
2782 def test_POST_mkdir_no_parentdir_noredirect2(self):
2783 # make sure form-based arguments (as on the welcome page) still work
2784 d = self.POST("/uri", t="mkdir")
2785 def _after_mkdir(res):
2786 uri.DirectoryURI.init_from_string(res)
2787 d.addCallback(_after_mkdir)
2788 d.addErrback(self.explain_web_error)
2791 def test_POST_mkdir_no_parentdir_redirect(self):
2792 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2793 d.addBoth(self.shouldRedirect, None, statuscode='303')
2794 def _check_target(target):
2795 target = urllib.unquote(target)
2796 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2797 d.addCallback(_check_target)
2800 def test_POST_mkdir_no_parentdir_redirect2(self):
2801 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2802 d.addBoth(self.shouldRedirect, None, statuscode='303')
2803 def _check_target(target):
2804 target = urllib.unquote(target)
2805 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2806 d.addCallback(_check_target)
2807 d.addErrback(self.explain_web_error)
2810 def _make_readonly(self, u):
2811 ro_uri = uri.from_string(u).get_readonly()
2814 return ro_uri.to_string()
2816 def _create_initial_children(self):
2817 contents, n, filecap1 = self.makefile(12)
2818 md1 = {"metakey1": "metavalue1"}
2819 filecap2 = make_mutable_file_uri()
2820 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2821 filecap3 = node3.get_readonly_uri()
2822 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2823 dircap = DirectoryNode(node4, None, None).get_uri()
2824 mdmfcap = make_mutable_file_uri(mdmf=True)
2825 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2826 emptydircap = "URI:DIR2-LIT:"
2827 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2828 "ro_uri": self._make_readonly(filecap1),
2829 "metadata": md1, }],
2830 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2831 "ro_uri": self._make_readonly(filecap2)}],
2832 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2833 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2834 "ro_uri": unknown_rocap}],
2835 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2836 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2837 u"dirchild": ["dirnode", {"rw_uri": dircap,
2838 "ro_uri": self._make_readonly(dircap)}],
2839 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2840 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2841 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2842 "ro_uri": self._make_readonly(mdmfcap)}],
2844 return newkids, {'filecap1': filecap1,
2845 'filecap2': filecap2,
2846 'filecap3': filecap3,
2847 'unknown_rwcap': unknown_rwcap,
2848 'unknown_rocap': unknown_rocap,
2849 'unknown_immcap': unknown_immcap,
2851 'litdircap': litdircap,
2852 'emptydircap': emptydircap,
2855 def _create_immutable_children(self):
2856 contents, n, filecap1 = self.makefile(12)
2857 md1 = {"metakey1": "metavalue1"}
2858 tnode = create_chk_filenode("immutable directory contents\n"*10)
2859 dnode = DirectoryNode(tnode, None, None)
2860 assert not dnode.is_mutable()
2861 immdircap = dnode.get_uri()
2862 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2863 emptydircap = "URI:DIR2-LIT:"
2864 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2865 "metadata": md1, }],
2866 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2867 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2868 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2869 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2871 return newkids, {'filecap1': filecap1,
2872 'unknown_immcap': unknown_immcap,
2873 'immdircap': immdircap,
2874 'litdircap': litdircap,
2875 'emptydircap': emptydircap}
2877 def test_POST_mkdir_no_parentdir_initial_children(self):
2878 (newkids, caps) = self._create_initial_children()
2879 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2880 def _after_mkdir(res):
2881 self.failUnless(res.startswith("URI:DIR"), res)
2882 n = self.s.create_node_from_uri(res)
2883 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2884 d2.addCallback(lambda ign:
2885 self.failUnlessROChildURIIs(n, u"child-imm",
2887 d2.addCallback(lambda ign:
2888 self.failUnlessRWChildURIIs(n, u"child-mutable",
2890 d2.addCallback(lambda ign:
2891 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2893 d2.addCallback(lambda ign:
2894 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2895 caps['unknown_rwcap']))
2896 d2.addCallback(lambda ign:
2897 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2898 caps['unknown_rocap']))
2899 d2.addCallback(lambda ign:
2900 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2901 caps['unknown_immcap']))
2902 d2.addCallback(lambda ign:
2903 self.failUnlessRWChildURIIs(n, u"dirchild",
2906 d.addCallback(_after_mkdir)
2909 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2910 # the regular /uri?t=mkdir operation is specified to ignore its body.
2911 # Only t=mkdir-with-children pays attention to it.
2912 (newkids, caps) = self._create_initial_children()
2913 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
2915 "t=mkdir does not accept children=, "
2916 "try t=mkdir-with-children instead",
2917 self.POST2, "/uri?t=mkdir", # without children
2918 simplejson.dumps(newkids))
2921 def test_POST_noparent_bad(self):
2922 d = self.shouldHTTPError("POST_noparent_bad",
2924 "/uri accepts only PUT, PUT?t=mkdir, "
2925 "POST?t=upload, and POST?t=mkdir",
2926 self.POST, "/uri?t=bogus")
2929 def test_POST_mkdir_no_parentdir_immutable(self):
2930 (newkids, caps) = self._create_immutable_children()
2931 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2932 def _after_mkdir(res):
2933 self.failUnless(res.startswith("URI:DIR"), res)
2934 n = self.s.create_node_from_uri(res)
2935 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2936 d2.addCallback(lambda ign:
2937 self.failUnlessROChildURIIs(n, u"child-imm",
2939 d2.addCallback(lambda ign:
2940 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2941 caps['unknown_immcap']))
2942 d2.addCallback(lambda ign:
2943 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2945 d2.addCallback(lambda ign:
2946 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2948 d2.addCallback(lambda ign:
2949 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2950 caps['emptydircap']))
2952 d.addCallback(_after_mkdir)
2955 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2956 (newkids, caps) = self._create_initial_children()
2957 d = self.shouldFail2(error.Error,
2958 "test_POST_mkdir_no_parentdir_immutable_bad",
2960 "needed to be immutable but was not",
2962 "/uri?t=mkdir-immutable",
2963 simplejson.dumps(newkids))
2966 def test_welcome_page_mkdir_button(self):
2967 # Fetch the welcome page.
2969 def _after_get_welcome_page(res):
2970 MKDIR_BUTTON_RE = re.compile(
2971 '<form action="([^"]*)" method="post".*?'
2972 '<input type="hidden" name="t" value="([^"]*)" />'
2973 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2974 '<input type="submit" value="Create a directory" />',
2976 mo = MKDIR_BUTTON_RE.search(res)
2977 formaction = mo.group(1)
2979 formaname = mo.group(3)
2980 formavalue = mo.group(4)
2981 return (formaction, formt, formaname, formavalue)
2982 d.addCallback(_after_get_welcome_page)
2983 def _after_parse_form(res):
2984 (formaction, formt, formaname, formavalue) = res
2985 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2986 d.addCallback(_after_parse_form)
2987 d.addBoth(self.shouldRedirect, None, statuscode='303')
2990 def test_POST_mkdir_replace(self): # return value?
2991 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2992 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2993 d.addCallback(self.failUnlessNodeKeysAre, [])
2996 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2997 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2998 d.addBoth(self.shouldFail, error.Error,
2999 "POST_mkdir_no_replace_queryarg",
3001 "There was already a child by that name, and you asked me "
3002 "to not replace it")
3003 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3004 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3007 def test_POST_mkdir_no_replace_field(self): # return value?
3008 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3010 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3012 "There was already a child by that name, and you asked me "
3013 "to not replace it")
3014 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3015 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3018 def test_POST_mkdir_whendone_field(self):
3019 d = self.POST(self.public_url + "/foo",
3020 t="mkdir", name="newdir", when_done="/THERE")
3021 d.addBoth(self.shouldRedirect, "/THERE")
3022 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3023 d.addCallback(self.failUnlessNodeKeysAre, [])
3026 def test_POST_mkdir_whendone_queryarg(self):
3027 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3028 t="mkdir", name="newdir")
3029 d.addBoth(self.shouldRedirect, "/THERE")
3030 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3031 d.addCallback(self.failUnlessNodeKeysAre, [])
3034 def test_POST_bad_t(self):
3035 d = self.shouldFail2(error.Error, "POST_bad_t",
3037 "POST to a directory with bad t=BOGUS",
3038 self.POST, self.public_url + "/foo", t="BOGUS")
3041 def test_POST_set_children(self, command_name="set_children"):
3042 contents9, n9, newuri9 = self.makefile(9)
3043 contents10, n10, newuri10 = self.makefile(10)
3044 contents11, n11, newuri11 = self.makefile(11)
3047 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3050 "ctime": 1002777696.7564139,
3051 "mtime": 1002777696.7564139
3054 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3057 "ctime": 1002777696.7564139,
3058 "mtime": 1002777696.7564139
3061 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3064 "ctime": 1002777696.7564139,
3065 "mtime": 1002777696.7564139
3068 }""" % (newuri9, newuri10, newuri11)
3070 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3072 d = client.getPage(url, method="POST", postdata=reqbody)
3074 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3075 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3076 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3078 d.addCallback(_then)
3079 d.addErrback(self.dump_error)
3082 def test_POST_set_children_with_hyphen(self):
3083 return self.test_POST_set_children(command_name="set-children")
3085 def test_POST_link_uri(self):
3086 contents, n, newuri = self.makefile(8)
3087 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3088 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3089 d.addCallback(lambda res:
3090 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3094 def test_POST_link_uri_replace(self):
3095 contents, n, newuri = self.makefile(8)
3096 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3097 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3098 d.addCallback(lambda res:
3099 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3103 def test_POST_link_uri_unknown_bad(self):
3104 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3105 d.addBoth(self.shouldFail, error.Error,
3106 "POST_link_uri_unknown_bad",
3108 "unknown cap in a write slot")
3111 def test_POST_link_uri_unknown_ro_good(self):
3112 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3113 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3116 def test_POST_link_uri_unknown_imm_good(self):
3117 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3118 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3121 def test_POST_link_uri_no_replace_queryarg(self):
3122 contents, n, newuri = self.makefile(8)
3123 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3124 name="bar.txt", uri=newuri)
3125 d.addBoth(self.shouldFail, error.Error,
3126 "POST_link_uri_no_replace_queryarg",
3128 "There was already a child by that name, and you asked me "
3129 "to not replace it")
3130 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3131 d.addCallback(self.failUnlessIsBarDotTxt)
3134 def test_POST_link_uri_no_replace_field(self):
3135 contents, n, newuri = self.makefile(8)
3136 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3137 name="bar.txt", uri=newuri)
3138 d.addBoth(self.shouldFail, error.Error,
3139 "POST_link_uri_no_replace_field",
3141 "There was already a child by that name, and you asked me "
3142 "to not replace it")
3143 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3144 d.addCallback(self.failUnlessIsBarDotTxt)
3147 def test_POST_delete(self, command_name='delete'):
3148 d = self._foo_node.list()
3149 def _check_before(children):
3150 self.failUnlessIn(u"bar.txt", children)
3151 d.addCallback(_check_before)
3152 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3153 d.addCallback(lambda res: self._foo_node.list())
3154 def _check_after(children):
3155 self.failIfIn(u"bar.txt", children)
3156 d.addCallback(_check_after)
3159 def test_POST_unlink(self):
3160 return self.test_POST_delete(command_name='unlink')
3162 def test_POST_rename_file(self):
3163 d = self.POST(self.public_url + "/foo", t="rename",
3164 from_name="bar.txt", to_name='wibble.txt')
3165 d.addCallback(lambda res:
3166 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3167 d.addCallback(lambda res:
3168 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3169 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3170 d.addCallback(self.failUnlessIsBarDotTxt)
3171 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3172 d.addCallback(self.failUnlessIsBarJSON)
3175 def test_POST_rename_file_redundant(self):
3176 d = self.POST(self.public_url + "/foo", t="rename",
3177 from_name="bar.txt", to_name='bar.txt')
3178 d.addCallback(lambda res:
3179 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3180 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3181 d.addCallback(self.failUnlessIsBarDotTxt)
3182 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3183 d.addCallback(self.failUnlessIsBarJSON)
3186 def test_POST_rename_file_replace(self):
3187 # rename a file and replace a directory with it
3188 d = self.POST(self.public_url + "/foo", t="rename",
3189 from_name="bar.txt", to_name='empty')
3190 d.addCallback(lambda res:
3191 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3192 d.addCallback(lambda res:
3193 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3194 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3195 d.addCallback(self.failUnlessIsBarDotTxt)
3196 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3197 d.addCallback(self.failUnlessIsBarJSON)
3200 def test_POST_rename_file_no_replace_queryarg(self):
3201 # rename a file and replace a directory with it
3202 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3203 from_name="bar.txt", to_name='empty')
3204 d.addBoth(self.shouldFail, error.Error,
3205 "POST_rename_file_no_replace_queryarg",
3207 "There was already a child by that name, and you asked me "
3208 "to not replace it")
3209 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3210 d.addCallback(self.failUnlessIsEmptyJSON)
3213 def test_POST_rename_file_no_replace_field(self):
3214 # rename a file and replace a directory with it
3215 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3216 from_name="bar.txt", to_name='empty')
3217 d.addBoth(self.shouldFail, error.Error,
3218 "POST_rename_file_no_replace_field",
3220 "There was already a child by that name, and you asked me "
3221 "to not replace it")
3222 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3223 d.addCallback(self.failUnlessIsEmptyJSON)
3226 def failUnlessIsEmptyJSON(self, res):
3227 data = simplejson.loads(res)
3228 self.failUnlessEqual(data[0], "dirnode", data)
3229 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3231 def test_POST_rename_file_slash_fail(self):
3232 d = self.POST(self.public_url + "/foo", t="rename",
3233 from_name="bar.txt", to_name='kirk/spock.txt')
3234 d.addBoth(self.shouldFail, error.Error,
3235 "test_POST_rename_file_slash_fail",
3237 "to_name= may not contain a slash",
3239 d.addCallback(lambda res:
3240 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3243 def test_POST_rename_dir(self):
3244 d = self.POST(self.public_url, t="rename",
3245 from_name="foo", to_name='plunk')
3246 d.addCallback(lambda res:
3247 self.failIfNodeHasChild(self.public_root, u"foo"))
3248 d.addCallback(lambda res:
3249 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3250 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3251 d.addCallback(self.failUnlessIsFooJSON)
3254 def test_POST_move_file(self):
3255 d = self.POST(self.public_url + "/foo", t="move",
3256 from_name="bar.txt", to_dir="sub")
3257 d.addCallback(lambda res:
3258 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3259 d.addCallback(lambda res:
3260 self.failUnlessNodeHasChild(self._sub_node, u"bar.txt"))
3261 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3262 d.addCallback(self.failUnlessIsBarDotTxt)
3263 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3264 d.addCallback(self.failUnlessIsBarJSON)
3267 def test_POST_move_file_new_name(self):
3268 d = self.POST(self.public_url + "/foo", t="move",
3269 from_name="bar.txt", to_name="wibble.txt", to_dir="sub")
3270 d.addCallback(lambda res:
3271 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3272 d.addCallback(lambda res:
3273 self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3274 d.addCallback(lambda res:
3275 self.failUnlessNodeHasChild(self._sub_node, u"wibble.txt"))
3276 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt"))
3277 d.addCallback(self.failUnlessIsBarDotTxt)
3278 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt?t=json"))
3279 d.addCallback(self.failUnlessIsBarJSON)
3282 def test_POST_move_file_replace(self):
3283 d = self.POST(self.public_url + "/foo", t="move",
3284 from_name="bar.txt", to_name="baz.txt", to_dir="sub")
3285 d.addCallback(lambda res:
3286 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3287 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3288 d.addCallback(self.failUnlessIsBarDotTxt)
3289 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3290 d.addCallback(self.failUnlessIsBarJSON)
3293 def test_POST_move_file_no_replace(self):
3294 d = self.shouldFail2(error.Error, "POST_move_file_no_replace",
3296 "There was already a child by that name, and you asked me to not replace it",
3297 self.POST, self.public_url + "/foo", t="move",
3298 replace="false", from_name="bar.txt",
3299 to_name="baz.txt", to_dir="sub")
3300 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3301 d.addCallback(self.failUnlessIsBarDotTxt)
3302 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3303 d.addCallback(self.failUnlessIsBarJSON)
3304 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3305 d.addCallback(self.failUnlessIsSubBazDotTxt)
3308 def test_POST_move_file_slash_fail(self):
3309 d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
3311 "to_name= may not contain a slash",
3312 self.POST, self.public_url + "/foo", t="move",
3313 from_name="bar.txt",
3314 to_name="slash/fail.txt", to_dir="sub")
3315 d.addCallback(lambda res:
3316 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3317 d.addCallback(lambda res:
3318 self.failIfNodeHasChild(self._sub_node, u"slash/fail.txt"))
3319 d.addCallback(lambda ign:
3320 self.shouldFail2(error.Error,
3321 "test_POST_rename_file_slash_fail2",
3323 "from_name= may not contain a slash",
3324 self.POST, self.public_url + "/foo",
3326 from_name="nope/bar.txt",
3327 to_name="fail.txt", to_dir="sub"))
3330 def test_POST_move_file_no_target(self):
3331 d = self.shouldFail2(error.Error, "POST_move_file_no_target",
3333 "move requires from_name and to_dir",
3334 self.POST, self.public_url + "/foo", t="move",
3335 from_name="bar.txt", to_name="baz.txt")
3336 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3337 d.addCallback(self.failUnlessIsBarDotTxt)
3338 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3339 d.addCallback(self.failUnlessIsBarJSON)
3340 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3341 d.addCallback(self.failUnlessIsBazDotTxt)
3344 def test_POST_move_file_bad_target_type(self):
3345 d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type",
3346 "400 Bad Request", "invalid target_type parameter",
3348 self.public_url + "/foo", t="move",
3349 target_type="*D", from_name="bar.txt",
3353 def test_POST_move_file_multi_level(self):
3354 d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
3355 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move",
3356 from_name="bar.txt", to_dir="sub/level2"))
3357 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3358 d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3359 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
3360 d.addCallback(self.failUnlessIsBarDotTxt)
3361 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt?t=json"))
3362 d.addCallback(self.failUnlessIsBarJSON)
3365 def test_POST_move_file_to_uri(self):
3366 d = self.POST(self.public_url + "/foo", t="move", target_type="uri",
3367 from_name="bar.txt", to_dir=self._sub_uri)
3368 d.addCallback(lambda res:
3369 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3370 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3371 d.addCallback(self.failUnlessIsBarDotTxt)
3372 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3373 d.addCallback(self.failUnlessIsBarJSON)
3376 def test_POST_move_file_to_nonexist_dir(self):
3377 d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir",
3378 "404 Not Found", "No such child: nopechucktesta",
3379 self.POST, self.public_url + "/foo", t="move",
3380 from_name="bar.txt", to_dir="nopechucktesta")
3383 def test_POST_move_file_into_file(self):
3384 d = self.shouldFail2(error.Error, "POST_move_file_into_file",
3385 "400 Bad Request", "to_dir is not a directory",
3386 self.POST, self.public_url + "/foo", t="move",
3387 from_name="bar.txt", to_dir="baz.txt")
3388 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3389 d.addCallback(self.failUnlessIsBazDotTxt)
3390 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3391 d.addCallback(self.failUnlessIsBarDotTxt)
3392 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3393 d.addCallback(self.failUnlessIsBarJSON)
3396 def test_POST_move_file_to_bad_uri(self):
3397 d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri",
3398 "400 Bad Request", "to_dir is not a directory",
3399 self.POST, self.public_url + "/foo", t="move",
3400 from_name="bar.txt", target_type="uri",
3401 to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
3402 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3403 d.addCallback(self.failUnlessIsBarDotTxt)
3404 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3405 d.addCallback(self.failUnlessIsBarJSON)
3408 def test_POST_move_dir(self):
3409 d = self.POST(self.public_url + "/foo", t="move",
3410 from_name="bar.txt", to_dir="empty")
3411 d.addCallback(lambda res: self.POST(self.public_url + "/foo",
3412 t="move", from_name="empty", to_dir="sub"))
3413 d.addCallback(lambda res:
3414 self.failIfNodeHasChild(self._foo_node, u"empty"))
3415 d.addCallback(lambda res:
3416 self.failUnlessNodeHasChild(self._sub_node, u"empty"))
3417 d.addCallback(lambda res:
3418 self._sub_node.get_child_at_path(u"empty"))
3419 d.addCallback(lambda node:
3420 self.failUnlessNodeHasChild(node, u"bar.txt"))
3421 d.addCallback(lambda res:
3422 self.GET(self.public_url + "/foo/sub/empty/bar.txt"))
3423 d.addCallback(self.failUnlessIsBarDotTxt)
3426 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3427 """ If target is not None then the redirection has to go to target. If
3428 statuscode is not None then the redirection has to be accomplished with
3429 that HTTP status code."""
3430 if not isinstance(res, failure.Failure):
3431 to_where = (target is None) and "somewhere" or ("to " + target)
3432 self.fail("%s: we were expecting to get redirected %s, not get an"
3433 " actual page: %s" % (which, to_where, res))
3434 res.trap(error.PageRedirect)
3435 if statuscode is not None:
3436 self.failUnlessReallyEqual(res.value.status, statuscode,
3437 "%s: not a redirect" % which)
3438 if target is not None:
3439 # the PageRedirect does not seem to capture the uri= query arg
3440 # properly, so we can't check for it.
3441 realtarget = self.webish_url + target
3442 self.failUnlessReallyEqual(res.value.location, realtarget,
3443 "%s: wrong target" % which)
3444 return res.value.location
3446 def test_GET_URI_form(self):
3447 base = "/uri?uri=%s" % self._bar_txt_uri
3448 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3449 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3451 d.addBoth(self.shouldRedirect, targetbase)
3452 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3453 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3454 d.addCallback(lambda res: self.GET(base+"&t=json"))
3455 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3456 d.addCallback(self.log, "about to get file by uri")
3457 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3458 d.addCallback(self.failUnlessIsBarDotTxt)
3459 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3460 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3461 followRedirect=True))
3462 d.addCallback(self.failUnlessIsFooJSON)
3463 d.addCallback(self.log, "got dir by uri")
3467 def test_GET_URI_form_bad(self):
3468 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3469 "400 Bad Request", "GET /uri requires uri=",
3473 def test_GET_rename_form(self):
3474 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3475 followRedirect=True)
3477 self.failUnlessIn('name="when_done" value="."', res)
3478 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3479 self.failUnlessIn(FAVICON_MARKUP, res)
3480 d.addCallback(_check)
3483 def test_GET_move_form(self):
3484 d = self.GET(self.public_url + "/foo?t=move-form&name=bar.txt",
3485 followRedirect=True)
3487 self.failUnless('name="when_done" value="."' in res, res)
3488 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3489 d.addCallback(_check)
3492 def log(self, res, msg):
3493 #print "MSG: %s RES: %s" % (msg, res)
3497 def test_GET_URI_URL(self):
3498 base = "/uri/%s" % self._bar_txt_uri
3500 d.addCallback(self.failUnlessIsBarDotTxt)
3501 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3502 d.addCallback(self.failUnlessIsBarDotTxt)
3503 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3504 d.addCallback(self.failUnlessIsBarDotTxt)
3507 def test_GET_URI_URL_dir(self):
3508 base = "/uri/%s?t=json" % self._foo_uri
3510 d.addCallback(self.failUnlessIsFooJSON)
3513 def test_GET_URI_URL_missing(self):
3514 base = "/uri/%s" % self._bad_file_uri
3515 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3516 http.GONE, None, "NotEnoughSharesError",
3518 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3519 # here? we must arrange for a download to fail after target.open()
3520 # has been called, and then inspect the response to see that it is
3521 # shorter than we expected.
3524 def test_PUT_DIRURL_uri(self):
3525 d = self.s.create_dirnode()
3527 new_uri = dn.get_uri()
3528 # replace /foo with a new (empty) directory
3529 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3530 d.addCallback(lambda res:
3531 self.failUnlessReallyEqual(res.strip(), new_uri))
3532 d.addCallback(lambda res:
3533 self.failUnlessRWChildURIIs(self.public_root,
3537 d.addCallback(_made_dir)
3540 def test_PUT_DIRURL_uri_noreplace(self):
3541 d = self.s.create_dirnode()
3543 new_uri = dn.get_uri()
3544 # replace /foo with a new (empty) directory, but ask that
3545 # replace=false, so it should fail
3546 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3547 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3549 self.public_url + "/foo?t=uri&replace=false",
3551 d.addCallback(lambda res:
3552 self.failUnlessRWChildURIIs(self.public_root,
3556 d.addCallback(_made_dir)
3559 def test_PUT_DIRURL_bad_t(self):
3560 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3561 "400 Bad Request", "PUT to a directory",
3562 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3563 d.addCallback(lambda res:
3564 self.failUnlessRWChildURIIs(self.public_root,
3569 def test_PUT_NEWFILEURL_uri(self):
3570 contents, n, new_uri = self.makefile(8)
3571 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3572 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3573 d.addCallback(lambda res:
3574 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3578 def test_PUT_NEWFILEURL_mdmf(self):
3579 new_contents = self.NEWFILE_CONTENTS * 300000
3580 d = self.PUT(self.public_url + \
3581 "/foo/mdmf.txt?format=mdmf",
3583 d.addCallback(lambda ignored:
3584 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3585 def _got_json(json):
3586 data = simplejson.loads(json)
3588 self.failUnlessIn("format", data)
3589 self.failUnlessEqual(data["format"], "MDMF")
3590 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3591 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3592 d.addCallback(_got_json)
3595 def test_PUT_NEWFILEURL_sdmf(self):
3596 new_contents = self.NEWFILE_CONTENTS * 300000
3597 d = self.PUT(self.public_url + \
3598 "/foo/sdmf.txt?format=sdmf",
3600 d.addCallback(lambda ignored:
3601 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3602 def _got_json(json):
3603 data = simplejson.loads(json)
3605 self.failUnlessIn("format", data)
3606 self.failUnlessEqual(data["format"], "SDMF")
3607 d.addCallback(_got_json)
3610 def test_PUT_NEWFILEURL_bad_format(self):
3611 new_contents = self.NEWFILE_CONTENTS * 300000
3612 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3613 400, "Bad Request", "Unknown format: foo",
3614 self.PUT, self.public_url + \
3615 "/foo/foo.txt?format=foo",
3618 def test_PUT_NEWFILEURL_uri_replace(self):
3619 contents, n, new_uri = self.makefile(8)
3620 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3621 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3622 d.addCallback(lambda res:
3623 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3627 def test_PUT_NEWFILEURL_uri_no_replace(self):
3628 contents, n, new_uri = self.makefile(8)
3629 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3630 d.addBoth(self.shouldFail, error.Error,
3631 "PUT_NEWFILEURL_uri_no_replace",
3633 "There was already a child by that name, and you asked me "
3634 "to not replace it")
3637 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3638 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3639 d.addBoth(self.shouldFail, error.Error,
3640 "POST_put_uri_unknown_bad",
3642 "unknown cap in a write slot")
3645 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3646 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3647 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3648 u"put-future-ro.txt")
3651 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3652 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3653 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3654 u"put-future-imm.txt")
3657 def test_PUT_NEWFILE_URI(self):
3658 file_contents = "New file contents here\n"
3659 d = self.PUT("/uri", file_contents)
3661 assert isinstance(uri, str), uri
3662 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3663 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3665 return self.GET("/uri/%s" % uri)
3666 d.addCallback(_check)
3668 self.failUnlessReallyEqual(res, file_contents)
3669 d.addCallback(_check2)
3672 def test_PUT_NEWFILE_URI_not_mutable(self):
3673 file_contents = "New file contents here\n"
3674 d = self.PUT("/uri?mutable=false", file_contents)
3676 assert isinstance(uri, str), uri
3677 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3678 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3680 return self.GET("/uri/%s" % uri)
3681 d.addCallback(_check)
3683 self.failUnlessReallyEqual(res, file_contents)
3684 d.addCallback(_check2)
3687 def test_PUT_NEWFILE_URI_only_PUT(self):
3688 d = self.PUT("/uri?t=bogus", "")
3689 d.addBoth(self.shouldFail, error.Error,
3690 "PUT_NEWFILE_URI_only_PUT",
3692 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3695 def test_PUT_NEWFILE_URI_mutable(self):
3696 file_contents = "New file contents here\n"
3697 d = self.PUT("/uri?mutable=true", file_contents)
3698 def _check1(filecap):
3699 filecap = filecap.strip()
3700 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3701 self.filecap = filecap
3702 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3703 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
3704 n = self.s.create_node_from_uri(filecap)
3705 return n.download_best_version()
3706 d.addCallback(_check1)
3708 self.failUnlessReallyEqual(data, file_contents)
3709 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3710 d.addCallback(_check2)
3712 self.failUnlessReallyEqual(res, file_contents)
3713 d.addCallback(_check3)
3716 def test_PUT_mkdir(self):
3717 d = self.PUT("/uri?t=mkdir", "")
3719 n = self.s.create_node_from_uri(uri.strip())
3720 d2 = self.failUnlessNodeKeysAre(n, [])
3721 d2.addCallback(lambda res:
3722 self.GET("/uri/%s?t=json" % uri))
3724 d.addCallback(_check)
3725 d.addCallback(self.failUnlessIsEmptyJSON)
3728 def test_PUT_mkdir_mdmf(self):
3729 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3731 u = uri.from_string(res)
3732 # Check that this is an MDMF writecap
3733 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3737 def test_PUT_mkdir_sdmf(self):
3738 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3740 u = uri.from_string(res)
3741 self.failUnlessIsInstance(u, uri.DirectoryURI)
3745 def test_PUT_mkdir_bad_format(self):
3746 return self.shouldHTTPError("PUT_mkdir_bad_format",
3747 400, "Bad Request", "Unknown format: foo",
3748 self.PUT, "/uri?t=mkdir&format=foo",
3751 def test_POST_check(self):
3752 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3754 # this returns a string form of the results, which are probably
3755 # None since we're using fake filenodes.
3756 # TODO: verify that the check actually happened, by changing
3757 # FakeCHKFileNode to count how many times .check() has been
3760 d.addCallback(_done)
3764 def test_PUT_update_at_offset(self):
3765 file_contents = "test file" * 100000 # about 900 KiB
3766 d = self.PUT("/uri?mutable=true", file_contents)
3768 self.filecap = filecap
3769 new_data = file_contents[:100]
3770 new = "replaced and so on"
3772 new_data += file_contents[len(new_data):]
3773 assert len(new_data) == len(file_contents)
3774 self.new_data = new_data
3775 d.addCallback(_then)
3776 d.addCallback(lambda ignored:
3777 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3778 "replaced and so on"))
3779 def _get_data(filecap):
3780 n = self.s.create_node_from_uri(filecap)
3781 return n.download_best_version()
3782 d.addCallback(_get_data)
3783 d.addCallback(lambda results:
3784 self.failUnlessEqual(results, self.new_data))
3785 # Now try appending things to the file
3786 d.addCallback(lambda ignored:
3787 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3789 d.addCallback(_get_data)
3790 d.addCallback(lambda results:
3791 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3792 # and try replacing the beginning of the file
3793 d.addCallback(lambda ignored:
3794 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3795 d.addCallback(_get_data)
3796 d.addCallback(lambda results:
3797 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3800 def test_PUT_update_at_invalid_offset(self):
3801 file_contents = "test file" * 100000 # about 900 KiB
3802 d = self.PUT("/uri?mutable=true", file_contents)
3804 self.filecap = filecap
3805 d.addCallback(_then)
3806 # Negative offsets should cause an error.
3807 d.addCallback(lambda ignored:
3808 self.shouldHTTPError("PUT_update_at_invalid_offset",
3812 "/uri/%s?offset=-1" % self.filecap,
3816 def test_PUT_update_at_offset_immutable(self):
3817 file_contents = "Test file" * 100000
3818 d = self.PUT("/uri", file_contents)
3820 self.filecap = filecap
3821 d.addCallback(_then)
3822 d.addCallback(lambda ignored:
3823 self.shouldHTTPError("PUT_update_at_offset_immutable",
3827 "/uri/%s?offset=50" % self.filecap,
3832 def test_bad_method(self):
3833 url = self.webish_url + self.public_url + "/foo/bar.txt"
3834 d = self.shouldHTTPError("bad_method",
3835 501, "Not Implemented",
3836 "I don't know how to treat a BOGUS request.",
3837 client.getPage, url, method="BOGUS")
3840 def test_short_url(self):
3841 url = self.webish_url + "/uri"
3842 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3843 "I don't know how to treat a DELETE request.",
3844 client.getPage, url, method="DELETE")
3847 def test_ophandle_bad(self):
3848 url = self.webish_url + "/operations/bogus?t=status"
3849 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3850 "unknown/expired handle 'bogus'",
3851 client.getPage, url)
3854 def test_ophandle_cancel(self):
3855 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3856 followRedirect=True)
3857 d.addCallback(lambda ignored:
3858 self.GET("/operations/128?t=status&output=JSON"))
3860 data = simplejson.loads(res)
3861 self.failUnless("finished" in data, res)
3862 monitor = self.ws.root.child_operations.handles["128"][0]
3863 d = self.POST("/operations/128?t=cancel&output=JSON")
3865 data = simplejson.loads(res)
3866 self.failUnless("finished" in data, res)
3867 # t=cancel causes the handle to be forgotten
3868 self.failUnless(monitor.is_cancelled())
3869 d.addCallback(_check2)
3871 d.addCallback(_check1)
3872 d.addCallback(lambda ignored:
3873 self.shouldHTTPError("ophandle_cancel",
3874 404, "404 Not Found",
3875 "unknown/expired handle '128'",
3877 "/operations/128?t=status&output=JSON"))
3880 def test_ophandle_retainfor(self):
3881 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3882 followRedirect=True)
3883 d.addCallback(lambda ignored:
3884 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3886 data = simplejson.loads(res)
3887 self.failUnless("finished" in data, res)
3888 d.addCallback(_check1)
3889 # the retain-for=0 will cause the handle to be expired very soon
3890 d.addCallback(lambda ign:
3891 self.clock.advance(2.0))
3892 d.addCallback(lambda ignored:
3893 self.shouldHTTPError("ophandle_retainfor",
3894 404, "404 Not Found",
3895 "unknown/expired handle '129'",
3897 "/operations/129?t=status&output=JSON"))
3900 def test_ophandle_release_after_complete(self):
3901 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3902 followRedirect=True)
3903 d.addCallback(self.wait_for_operation, "130")
3904 d.addCallback(lambda ignored:
3905 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3906 # the release-after-complete=true will cause the handle to be expired
3907 d.addCallback(lambda ignored:
3908 self.shouldHTTPError("ophandle_release_after_complete",
3909 404, "404 Not Found",
3910 "unknown/expired handle '130'",
3912 "/operations/130?t=status&output=JSON"))
3915 def test_uncollected_ophandle_expiration(self):
3916 # uncollected ophandles should expire after 4 days
3917 def _make_uncollected_ophandle(ophandle):
3918 d = self.POST(self.public_url +
3919 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3920 followRedirect=False)
3921 # When we start the operation, the webapi server will want
3922 # to redirect us to the page for the ophandle, so we get
3923 # confirmation that the operation has started. If the
3924 # manifest operation has finished by the time we get there,
3925 # following that redirect (by setting followRedirect=True
3926 # above) has the side effect of collecting the ophandle that
3927 # we've just created, which means that we can't use the
3928 # ophandle to test the uncollected timeout anymore. So,
3929 # instead, catch the 302 here and don't follow it.
3930 d.addBoth(self.should302, "uncollected_ophandle_creation")
3932 # Create an ophandle, don't collect it, then advance the clock by
3933 # 4 days - 1 second and make sure that the ophandle is still there.
3934 d = _make_uncollected_ophandle(131)
3935 d.addCallback(lambda ign:
3936 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3937 d.addCallback(lambda ign:
3938 self.GET("/operations/131?t=status&output=JSON"))
3940 data = simplejson.loads(res)
3941 self.failUnless("finished" in data, res)
3942 d.addCallback(_check1)
3943 # Create an ophandle, don't collect it, then try to collect it
3944 # after 4 days. It should be gone.
3945 d.addCallback(lambda ign:
3946 _make_uncollected_ophandle(132))
3947 d.addCallback(lambda ign:
3948 self.clock.advance(96*60*60))
3949 d.addCallback(lambda ign:
3950 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3951 404, "404 Not Found",
3952 "unknown/expired handle '132'",
3954 "/operations/132?t=status&output=JSON"))
3957 def test_collected_ophandle_expiration(self):
3958 # collected ophandles should expire after 1 day
3959 def _make_collected_ophandle(ophandle):
3960 d = self.POST(self.public_url +
3961 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3962 followRedirect=True)
3963 # By following the initial redirect, we collect the ophandle
3964 # we've just created.
3966 # Create a collected ophandle, then collect it after 23 hours
3967 # and 59 seconds to make sure that it is still there.
3968 d = _make_collected_ophandle(133)
3969 d.addCallback(lambda ign:
3970 self.clock.advance((24*60*60) - 1))
3971 d.addCallback(lambda ign:
3972 self.GET("/operations/133?t=status&output=JSON"))
3974 data = simplejson.loads(res)
3975 self.failUnless("finished" in data, res)
3976 d.addCallback(_check1)
3977 # Create another uncollected ophandle, then try to collect it
3978 # after 24 hours to make sure that it is gone.
3979 d.addCallback(lambda ign:
3980 _make_collected_ophandle(134))
3981 d.addCallback(lambda ign:
3982 self.clock.advance(24*60*60))
3983 d.addCallback(lambda ign:
3984 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
3985 404, "404 Not Found",
3986 "unknown/expired handle '134'",
3988 "/operations/134?t=status&output=JSON"))
3991 def test_incident(self):
3992 d = self.POST("/report_incident", details="eek")
3994 self.failIfIn("<html>", res)
3995 self.failUnlessIn("Thank you for your report!", res)
3996 d.addCallback(_done)
3999 def test_static(self):
4000 webdir = os.path.join(self.staticdir, "subdir")
4001 fileutil.make_dirs(webdir)
4002 f = open(os.path.join(webdir, "hello.txt"), "wb")
4006 d = self.GET("/static/subdir/hello.txt")
4008 self.failUnlessReallyEqual(res, "hello")
4009 d.addCallback(_check)
4013 class IntroducerWeb(unittest.TestCase):
4018 d = defer.succeed(None)
4020 d.addCallback(lambda ign: self.node.stopService())
4021 d.addCallback(flushEventualQueue)
4024 def test_welcome(self):
4025 basedir = "web.IntroducerWeb.test_welcome"
4027 fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n")
4028 self.node = IntroducerNode(basedir)
4029 self.ws = self.node.getServiceNamed("webish")
4031 d = fireEventually(None)
4032 d.addCallback(lambda ign: self.node.startService())
4033 d.addCallback(lambda ign: self.node.when_tub_ready())
4035 d.addCallback(lambda ign: self.GET("/"))
4037 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
4038 self.failUnlessIn(FAVICON_MARKUP, res)
4039 d.addCallback(_check)
4042 def GET(self, urlpath, followRedirect=False, return_response=False,
4044 # if return_response=True, this fires with (data, statuscode,
4045 # respheaders) instead of just data.
4046 assert not isinstance(urlpath, unicode)
4047 url = self.ws.getURL().rstrip('/') + urlpath
4048 factory = HTTPClientGETFactory(url, method="GET",
4049 followRedirect=followRedirect, **kwargs)
4050 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
4051 d = factory.deferred
4052 def _got_data(data):
4053 return (data, factory.status, factory.response_headers)
4055 d.addCallback(_got_data)
4056 return factory.deferred
4059 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4060 def test_load_file(self):
4061 # This will raise an exception unless a well-formed XML file is found under that name.
4062 common.getxmlfile('directory.xhtml').load()
4064 def test_parse_replace_arg(self):
4065 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4066 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4067 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4069 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
4070 common.parse_replace_arg, "only_fles")
4072 def test_abbreviate_time(self):
4073 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4074 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4075 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4076 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4077 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4078 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4080 def test_compute_rate(self):
4081 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4082 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4083 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4084 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4085 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4086 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4087 self.shouldFail(AssertionError, "test_compute_rate", "",
4088 common.compute_rate, -100, 10)
4089 self.shouldFail(AssertionError, "test_compute_rate", "",
4090 common.compute_rate, 100, -10)
4093 rate = common.compute_rate(10*1000*1000, 1)
4094 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4096 def test_abbreviate_rate(self):
4097 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4098 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4099 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4100 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4102 def test_abbreviate_size(self):
4103 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4104 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4105 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4106 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4107 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4109 def test_plural(self):
4111 return "%d second%s" % (s, status.plural(s))
4112 self.failUnlessReallyEqual(convert(0), "0 seconds")
4113 self.failUnlessReallyEqual(convert(1), "1 second")
4114 self.failUnlessReallyEqual(convert(2), "2 seconds")
4116 return "has share%s: %s" % (status.plural(s), ",".join(s))
4117 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4118 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4119 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4122 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4124 def CHECK(self, ign, which, args, clientnum=0):
4125 fileurl = self.fileurls[which]
4126 url = fileurl + "?" + args
4127 return self.GET(url, method="POST", clientnum=clientnum)
4129 def test_filecheck(self):
4130 self.basedir = "web/Grid/filecheck"
4132 c0 = self.g.clients[0]
4135 d = c0.upload(upload.Data(DATA, convergence=""))
4136 def _stash_uri(ur, which):
4137 self.uris[which] = ur.uri
4138 d.addCallback(_stash_uri, "good")
4139 d.addCallback(lambda ign:
4140 c0.upload(upload.Data(DATA+"1", convergence="")))
4141 d.addCallback(_stash_uri, "sick")
4142 d.addCallback(lambda ign:
4143 c0.upload(upload.Data(DATA+"2", convergence="")))
4144 d.addCallback(_stash_uri, "dead")
4145 def _stash_mutable_uri(n, which):
4146 self.uris[which] = n.get_uri()
4147 assert isinstance(self.uris[which], str)
4148 d.addCallback(lambda ign:
4149 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4150 d.addCallback(_stash_mutable_uri, "corrupt")
4151 d.addCallback(lambda ign:
4152 c0.upload(upload.Data("literal", convergence="")))
4153 d.addCallback(_stash_uri, "small")
4154 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4155 d.addCallback(_stash_mutable_uri, "smalldir")
4157 def _compute_fileurls(ignored):
4159 for which in self.uris:
4160 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4161 d.addCallback(_compute_fileurls)
4163 def _clobber_shares(ignored):
4164 good_shares = self.find_uri_shares(self.uris["good"])
4165 self.failUnlessReallyEqual(len(good_shares), 10)
4166 sick_shares = self.find_uri_shares(self.uris["sick"])
4167 os.unlink(sick_shares[0][2])
4168 dead_shares = self.find_uri_shares(self.uris["dead"])
4169 for i in range(1, 10):
4170 os.unlink(dead_shares[i][2])
4171 c_shares = self.find_uri_shares(self.uris["corrupt"])
4172 cso = CorruptShareOptions()
4173 cso.stdout = StringIO()
4174 cso.parseOptions([c_shares[0][2]])
4176 d.addCallback(_clobber_shares)
4178 d.addCallback(self.CHECK, "good", "t=check")
4179 def _got_html_good(res):
4180 self.failUnlessIn("Healthy", res)
4181 self.failIfIn("Not Healthy", res)
4182 self.failUnlessIn(FAVICON_MARKUP, res)
4183 d.addCallback(_got_html_good)
4184 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4185 def _got_html_good_return_to(res):
4186 self.failUnlessIn("Healthy", res)
4187 self.failIfIn("Not Healthy", res)
4188 self.failUnlessIn('<a href="somewhere">Return to file', res)
4189 d.addCallback(_got_html_good_return_to)
4190 d.addCallback(self.CHECK, "good", "t=check&output=json")
4191 def _got_json_good(res):
4192 r = simplejson.loads(res)
4193 self.failUnlessEqual(r["summary"], "Healthy")
4194 self.failUnless(r["results"]["healthy"])
4195 self.failIf(r["results"]["needs-rebalancing"])
4196 self.failUnless(r["results"]["recoverable"])
4197 d.addCallback(_got_json_good)
4199 d.addCallback(self.CHECK, "small", "t=check")
4200 def _got_html_small(res):
4201 self.failUnlessIn("Literal files are always healthy", res)
4202 self.failIfIn("Not Healthy", res)
4203 d.addCallback(_got_html_small)
4204 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4205 def _got_html_small_return_to(res):
4206 self.failUnlessIn("Literal files are always healthy", res)
4207 self.failIfIn("Not Healthy", res)
4208 self.failUnlessIn('<a href="somewhere">Return to file', res)
4209 d.addCallback(_got_html_small_return_to)
4210 d.addCallback(self.CHECK, "small", "t=check&output=json")
4211 def _got_json_small(res):
4212 r = simplejson.loads(res)
4213 self.failUnlessEqual(r["storage-index"], "")
4214 self.failUnless(r["results"]["healthy"])
4215 d.addCallback(_got_json_small)
4217 d.addCallback(self.CHECK, "smalldir", "t=check")
4218 def _got_html_smalldir(res):
4219 self.failUnlessIn("Literal files are always healthy", res)
4220 self.failIfIn("Not Healthy", res)
4221 d.addCallback(_got_html_smalldir)
4222 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4223 def _got_json_smalldir(res):
4224 r = simplejson.loads(res)
4225 self.failUnlessEqual(r["storage-index"], "")
4226 self.failUnless(r["results"]["healthy"])
4227 d.addCallback(_got_json_smalldir)
4229 d.addCallback(self.CHECK, "sick", "t=check")
4230 def _got_html_sick(res):
4231 self.failUnlessIn("Not Healthy", res)
4232 d.addCallback(_got_html_sick)
4233 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4234 def _got_json_sick(res):
4235 r = simplejson.loads(res)
4236 self.failUnlessEqual(r["summary"],
4237 "Not Healthy: 9 shares (enc 3-of-10)")
4238 self.failIf(r["results"]["healthy"])
4239 self.failIf(r["results"]["needs-rebalancing"])
4240 self.failUnless(r["results"]["recoverable"])
4241 d.addCallback(_got_json_sick)
4243 d.addCallback(self.CHECK, "dead", "t=check")
4244 def _got_html_dead(res):
4245 self.failUnlessIn("Not Healthy", res)
4246 d.addCallback(_got_html_dead)
4247 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4248 def _got_json_dead(res):
4249 r = simplejson.loads(res)
4250 self.failUnlessEqual(r["summary"],
4251 "Not Healthy: 1 shares (enc 3-of-10)")
4252 self.failIf(r["results"]["healthy"])
4253 self.failIf(r["results"]["needs-rebalancing"])
4254 self.failIf(r["results"]["recoverable"])
4255 d.addCallback(_got_json_dead)
4257 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4258 def _got_html_corrupt(res):
4259 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4260 d.addCallback(_got_html_corrupt)
4261 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4262 def _got_json_corrupt(res):
4263 r = simplejson.loads(res)
4264 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4265 self.failIf(r["results"]["healthy"])
4266 self.failUnless(r["results"]["recoverable"])
4267 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4268 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4269 d.addCallback(_got_json_corrupt)
4271 d.addErrback(self.explain_web_error)
4274 def test_repair_html(self):
4275 self.basedir = "web/Grid/repair_html"
4277 c0 = self.g.clients[0]
4280 d = c0.upload(upload.Data(DATA, convergence=""))
4281 def _stash_uri(ur, which):
4282 self.uris[which] = ur.uri
4283 d.addCallback(_stash_uri, "good")
4284 d.addCallback(lambda ign:
4285 c0.upload(upload.Data(DATA+"1", convergence="")))
4286 d.addCallback(_stash_uri, "sick")
4287 d.addCallback(lambda ign:
4288 c0.upload(upload.Data(DATA+"2", convergence="")))
4289 d.addCallback(_stash_uri, "dead")
4290 def _stash_mutable_uri(n, which):
4291 self.uris[which] = n.get_uri()
4292 assert isinstance(self.uris[which], str)
4293 d.addCallback(lambda ign:
4294 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4295 d.addCallback(_stash_mutable_uri, "corrupt")
4297 def _compute_fileurls(ignored):
4299 for which in self.uris:
4300 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4301 d.addCallback(_compute_fileurls)
4303 def _clobber_shares(ignored):
4304 good_shares = self.find_uri_shares(self.uris["good"])
4305 self.failUnlessReallyEqual(len(good_shares), 10)
4306 sick_shares = self.find_uri_shares(self.uris["sick"])
4307 os.unlink(sick_shares[0][2])
4308 dead_shares = self.find_uri_shares(self.uris["dead"])
4309 for i in range(1, 10):
4310 os.unlink(dead_shares[i][2])
4311 c_shares = self.find_uri_shares(self.uris["corrupt"])
4312 cso = CorruptShareOptions()
4313 cso.stdout = StringIO()
4314 cso.parseOptions([c_shares[0][2]])
4316 d.addCallback(_clobber_shares)
4318 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4319 def _got_html_good(res):
4320 self.failUnlessIn("Healthy", res)
4321 self.failIfIn("Not Healthy", res)
4322 self.failUnlessIn("No repair necessary", res)
4323 self.failUnlessIn(FAVICON_MARKUP, res)
4324 d.addCallback(_got_html_good)
4326 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4327 def _got_html_sick(res):
4328 self.failUnlessIn("Healthy : healthy", res)
4329 self.failIfIn("Not Healthy", res)
4330 self.failUnlessIn("Repair successful", res)
4331 d.addCallback(_got_html_sick)
4333 # repair of a dead file will fail, of course, but it isn't yet
4334 # clear how this should be reported. Right now it shows up as
4337 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4338 #def _got_html_dead(res):
4340 # self.failUnlessIn("Healthy : healthy", res)
4341 # self.failIfIn("Not Healthy", res)
4342 # self.failUnlessIn("No repair necessary", res)
4343 #d.addCallback(_got_html_dead)
4345 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4346 def _got_html_corrupt(res):
4347 self.failUnlessIn("Healthy : Healthy", res)
4348 self.failIfIn("Not Healthy", res)
4349 self.failUnlessIn("Repair successful", res)
4350 d.addCallback(_got_html_corrupt)
4352 d.addErrback(self.explain_web_error)
4355 def test_repair_json(self):
4356 self.basedir = "web/Grid/repair_json"
4358 c0 = self.g.clients[0]
4361 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4362 def _stash_uri(ur, which):
4363 self.uris[which] = ur.uri
4364 d.addCallback(_stash_uri, "sick")
4366 def _compute_fileurls(ignored):
4368 for which in self.uris:
4369 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4370 d.addCallback(_compute_fileurls)
4372 def _clobber_shares(ignored):
4373 sick_shares = self.find_uri_shares(self.uris["sick"])
4374 os.unlink(sick_shares[0][2])
4375 d.addCallback(_clobber_shares)
4377 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4378 def _got_json_sick(res):
4379 r = simplejson.loads(res)
4380 self.failUnlessReallyEqual(r["repair-attempted"], True)
4381 self.failUnlessReallyEqual(r["repair-successful"], True)
4382 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4383 "Not Healthy: 9 shares (enc 3-of-10)")
4384 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4385 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4386 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4387 d.addCallback(_got_json_sick)
4389 d.addErrback(self.explain_web_error)
4392 def test_unknown(self, immutable=False):
4393 self.basedir = "web/Grid/unknown"
4395 self.basedir = "web/Grid/unknown-immutable"
4398 c0 = self.g.clients[0]
4402 # the future cap format may contain slashes, which must be tolerated
4403 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4407 name = u"future-imm"
4408 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4409 d = c0.create_immutable_dirnode({name: (future_node, {})})
4412 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4413 d = c0.create_dirnode()
4415 def _stash_root_and_create_file(n):
4417 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4418 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4420 return self.rootnode.set_node(name, future_node)
4421 d.addCallback(_stash_root_and_create_file)
4423 # make sure directory listing tolerates unknown nodes
4424 d.addCallback(lambda ign: self.GET(self.rooturl))
4425 def _check_directory_html(res, expected_type_suffix):
4426 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4427 '<td>%s</td>' % (expected_type_suffix, str(name)),
4429 self.failUnless(re.search(pattern, res), res)
4430 # find the More Info link for name, should be relative
4431 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4432 info_url = mo.group(1)
4433 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4435 d.addCallback(_check_directory_html, "-IMM")
4437 d.addCallback(_check_directory_html, "")
4439 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4440 def _check_directory_json(res, expect_rw_uri):
4441 data = simplejson.loads(res)
4442 self.failUnlessEqual(data[0], "dirnode")
4443 f = data[1]["children"][name]
4444 self.failUnlessEqual(f[0], "unknown")
4446 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4448 self.failIfIn("rw_uri", f[1])
4450 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4452 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4453 self.failUnlessIn("metadata", f[1])
4454 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4456 def _check_info(res, expect_rw_uri, expect_ro_uri):
4457 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4459 self.failUnlessIn(unknown_rwcap, res)
4462 self.failUnlessIn(unknown_immcap, res)
4464 self.failUnlessIn(unknown_rocap, res)
4466 self.failIfIn(unknown_rocap, res)
4467 self.failIfIn("Raw data as", res)
4468 self.failIfIn("Directory writecap", res)
4469 self.failIfIn("Checker Operations", res)
4470 self.failIfIn("Mutable File Operations", res)
4471 self.failIfIn("Directory Operations", res)
4473 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4474 # why they fail. Possibly related to ticket #922.
4476 d.addCallback(lambda ign: self.GET(expected_info_url))
4477 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4478 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4479 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4481 def _check_json(res, expect_rw_uri):
4482 data = simplejson.loads(res)
4483 self.failUnlessEqual(data[0], "unknown")
4485 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4487 self.failIfIn("rw_uri", data[1])
4490 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4491 self.failUnlessReallyEqual(data[1]["mutable"], False)
4493 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4494 self.failUnlessReallyEqual(data[1]["mutable"], True)
4496 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4497 self.failIfIn("mutable", data[1])
4499 # TODO: check metadata contents
4500 self.failUnlessIn("metadata", data[1])
4502 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4503 d.addCallback(_check_json, expect_rw_uri=not immutable)
4505 # and make sure that a read-only version of the directory can be
4506 # rendered too. This version will not have unknown_rwcap, whether
4507 # or not future_node was immutable.
4508 d.addCallback(lambda ign: self.GET(self.rourl))
4510 d.addCallback(_check_directory_html, "-IMM")
4512 d.addCallback(_check_directory_html, "-RO")
4514 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4515 d.addCallback(_check_directory_json, expect_rw_uri=False)
4517 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4518 d.addCallback(_check_json, expect_rw_uri=False)
4520 # TODO: check that getting t=info from the Info link in the ro directory
4521 # works, and does not include the writecap URI.
4524 def test_immutable_unknown(self):
4525 return self.test_unknown(immutable=True)
4527 def test_mutant_dirnodes_are_omitted(self):
4528 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4531 c = self.g.clients[0]
4536 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4537 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4538 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4540 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4541 # test the dirnode and web layers separately.
4543 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4544 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4545 # When the directory is read, the mutants should be silently disposed of, leaving
4546 # their lonely sibling.
4547 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4548 # because immutable directories don't have a writecap and therefore that field
4549 # isn't (and can't be) decrypted.
4550 # TODO: The field still exists in the netstring. Technically we should check what
4551 # happens if something is put there (_unpack_contents should raise ValueError),
4552 # but that can wait.
4554 lonely_child = nm.create_from_cap(lonely_uri)
4555 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4556 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4558 def _by_hook_or_by_crook():
4560 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4561 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4563 mutant_write_in_ro_child.get_write_uri = lambda: None
4564 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4566 kids = {u"lonely": (lonely_child, {}),
4567 u"ro": (mutant_ro_child, {}),
4568 u"write-in-ro": (mutant_write_in_ro_child, {}),
4570 d = c.create_immutable_dirnode(kids)
4573 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4574 self.failIf(dn.is_mutable())
4575 self.failUnless(dn.is_readonly())
4576 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4577 self.failIf(hasattr(dn._node, 'get_writekey'))
4579 self.failUnlessIn("RO-IMM", rep)
4581 self.failUnlessIn("CHK", cap.to_string())
4584 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4585 return download_to_data(dn._node)
4586 d.addCallback(_created)
4588 def _check_data(data):
4589 # Decode the netstring representation of the directory to check that all children
4590 # are present. This is a bit of an abstraction violation, but there's not really
4591 # any other way to do it given that the real DirectoryNode._unpack_contents would
4592 # strip the mutant children out (which is what we're trying to test, later).
4595 while position < len(data):
4596 entries, position = split_netstring(data, 1, position)
4598 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4599 name = name_utf8.decode("utf-8")
4600 self.failUnlessEqual(rwcapdata, "")
4601 self.failUnlessIn(name, kids)
4602 (expected_child, ign) = kids[name]
4603 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4606 self.failUnlessReallyEqual(numkids, 3)
4607 return self.rootnode.list()
4608 d.addCallback(_check_data)
4610 # Now when we use the real directory listing code, the mutants should be absent.
4611 def _check_kids(children):
4612 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4613 lonely_node, lonely_metadata = children[u"lonely"]
4615 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4616 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4617 d.addCallback(_check_kids)
4619 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4620 d.addCallback(lambda n: n.list())
4621 d.addCallback(_check_kids) # again with dirnode recreated from cap
4623 # Make sure the lonely child can be listed in HTML...
4624 d.addCallback(lambda ign: self.GET(self.rooturl))
4625 def _check_html(res):
4626 self.failIfIn("URI:SSK", res)
4627 get_lonely = "".join([r'<td>FILE</td>',
4629 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4631 r'\s+<td align="right">%d</td>' % len("one"),
4633 self.failUnless(re.search(get_lonely, res), res)
4635 # find the More Info link for name, should be relative
4636 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4637 info_url = mo.group(1)
4638 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4639 d.addCallback(_check_html)
4642 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4643 def _check_json(res):
4644 data = simplejson.loads(res)
4645 self.failUnlessEqual(data[0], "dirnode")
4646 listed_children = data[1]["children"]
4647 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4648 ll_type, ll_data = listed_children[u"lonely"]
4649 self.failUnlessEqual(ll_type, "filenode")
4650 self.failIfIn("rw_uri", ll_data)
4651 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4652 d.addCallback(_check_json)
4655 def test_deep_check(self):
4656 self.basedir = "web/Grid/deep_check"
4658 c0 = self.g.clients[0]
4662 d = c0.create_dirnode()
4663 def _stash_root_and_create_file(n):
4665 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4666 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4667 d.addCallback(_stash_root_and_create_file)
4668 def _stash_uri(fn, which):
4669 self.uris[which] = fn.get_uri()
4671 d.addCallback(_stash_uri, "good")
4672 d.addCallback(lambda ign:
4673 self.rootnode.add_file(u"small",
4674 upload.Data("literal",
4676 d.addCallback(_stash_uri, "small")
4677 d.addCallback(lambda ign:
4678 self.rootnode.add_file(u"sick",
4679 upload.Data(DATA+"1",
4681 d.addCallback(_stash_uri, "sick")
4683 # this tests that deep-check and stream-manifest will ignore
4684 # UnknownNode instances. Hopefully this will also cover deep-stats.
4685 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4686 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4688 def _clobber_shares(ignored):
4689 self.delete_shares_numbered(self.uris["sick"], [0,1])
4690 d.addCallback(_clobber_shares)
4698 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4701 units = [simplejson.loads(line)
4702 for line in res.splitlines()
4705 print "response is:", res
4706 print "undecodeable line was '%s'" % line
4708 self.failUnlessReallyEqual(len(units), 5+1)
4709 # should be parent-first
4711 self.failUnlessEqual(u0["path"], [])
4712 self.failUnlessEqual(u0["type"], "directory")
4713 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4714 u0cr = u0["check-results"]
4715 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4717 ugood = [u for u in units
4718 if u["type"] == "file" and u["path"] == [u"good"]][0]
4719 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4720 ugoodcr = ugood["check-results"]
4721 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4724 self.failUnlessEqual(stats["type"], "stats")
4726 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4727 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4728 self.failUnlessReallyEqual(s["count-directories"], 1)
4729 self.failUnlessReallyEqual(s["count-unknown"], 1)
4730 d.addCallback(_done)
4732 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4733 def _check_manifest(res):
4734 self.failUnless(res.endswith("\n"))
4735 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4736 self.failUnlessReallyEqual(len(units), 5+1)
4737 self.failUnlessEqual(units[-1]["type"], "stats")
4739 self.failUnlessEqual(first["path"], [])
4740 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4741 self.failUnlessEqual(first["type"], "directory")
4742 stats = units[-1]["stats"]
4743 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4744 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4745 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4746 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4747 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4748 d.addCallback(_check_manifest)
4750 # now add root/subdir and root/subdir/grandchild, then make subdir
4751 # unrecoverable, then see what happens
4753 d.addCallback(lambda ign:
4754 self.rootnode.create_subdirectory(u"subdir"))
4755 d.addCallback(_stash_uri, "subdir")
4756 d.addCallback(lambda subdir_node:
4757 subdir_node.add_file(u"grandchild",
4758 upload.Data(DATA+"2",
4760 d.addCallback(_stash_uri, "grandchild")
4762 d.addCallback(lambda ign:
4763 self.delete_shares_numbered(self.uris["subdir"],
4771 # root/subdir [unrecoverable]
4772 # root/subdir/grandchild
4774 # how should a streaming-JSON API indicate fatal error?
4775 # answer: emit ERROR: instead of a JSON string
4777 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4778 def _check_broken_manifest(res):
4779 lines = res.splitlines()
4781 for (i,line) in enumerate(lines)
4782 if line.startswith("ERROR:")]
4784 self.fail("no ERROR: in output: %s" % (res,))
4785 first_error = error_lines[0]
4786 error_line = lines[first_error]
4787 error_msg = lines[first_error+1:]
4788 error_msg_s = "\n".join(error_msg) + "\n"
4789 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4791 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4792 units = [simplejson.loads(line) for line in lines[:first_error]]
4793 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4794 last_unit = units[-1]
4795 self.failUnlessEqual(last_unit["path"], ["subdir"])
4796 d.addCallback(_check_broken_manifest)
4798 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4799 def _check_broken_deepcheck(res):
4800 lines = res.splitlines()
4802 for (i,line) in enumerate(lines)
4803 if line.startswith("ERROR:")]
4805 self.fail("no ERROR: in output: %s" % (res,))
4806 first_error = error_lines[0]
4807 error_line = lines[first_error]
4808 error_msg = lines[first_error+1:]
4809 error_msg_s = "\n".join(error_msg) + "\n"
4810 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4812 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4813 units = [simplejson.loads(line) for line in lines[:first_error]]
4814 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4815 last_unit = units[-1]
4816 self.failUnlessEqual(last_unit["path"], ["subdir"])
4817 r = last_unit["check-results"]["results"]
4818 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4819 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4820 self.failUnlessReallyEqual(r["recoverable"], False)
4821 d.addCallback(_check_broken_deepcheck)
4823 d.addErrback(self.explain_web_error)
4826 def test_deep_check_and_repair(self):
4827 self.basedir = "web/Grid/deep_check_and_repair"
4829 c0 = self.g.clients[0]
4833 d = c0.create_dirnode()
4834 def _stash_root_and_create_file(n):
4836 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4837 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4838 d.addCallback(_stash_root_and_create_file)
4839 def _stash_uri(fn, which):
4840 self.uris[which] = fn.get_uri()
4841 d.addCallback(_stash_uri, "good")
4842 d.addCallback(lambda ign:
4843 self.rootnode.add_file(u"small",
4844 upload.Data("literal",
4846 d.addCallback(_stash_uri, "small")
4847 d.addCallback(lambda ign:
4848 self.rootnode.add_file(u"sick",
4849 upload.Data(DATA+"1",
4851 d.addCallback(_stash_uri, "sick")
4852 #d.addCallback(lambda ign:
4853 # self.rootnode.add_file(u"dead",
4854 # upload.Data(DATA+"2",
4856 #d.addCallback(_stash_uri, "dead")
4858 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4859 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4860 #d.addCallback(_stash_uri, "corrupt")
4862 def _clobber_shares(ignored):
4863 good_shares = self.find_uri_shares(self.uris["good"])
4864 self.failUnlessReallyEqual(len(good_shares), 10)
4865 sick_shares = self.find_uri_shares(self.uris["sick"])
4866 os.unlink(sick_shares[0][2])
4867 #dead_shares = self.find_uri_shares(self.uris["dead"])
4868 #for i in range(1, 10):
4869 # os.unlink(dead_shares[i][2])
4871 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4872 #cso = CorruptShareOptions()
4873 #cso.stdout = StringIO()
4874 #cso.parseOptions([c_shares[0][2]])
4876 d.addCallback(_clobber_shares)
4879 # root/good CHK, 10 shares
4881 # root/sick CHK, 9 shares
4883 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4885 units = [simplejson.loads(line)
4886 for line in res.splitlines()
4888 self.failUnlessReallyEqual(len(units), 4+1)
4889 # should be parent-first
4891 self.failUnlessEqual(u0["path"], [])
4892 self.failUnlessEqual(u0["type"], "directory")
4893 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4894 u0crr = u0["check-and-repair-results"]
4895 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4896 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4898 ugood = [u for u in units
4899 if u["type"] == "file" and u["path"] == [u"good"]][0]
4900 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4901 ugoodcrr = ugood["check-and-repair-results"]
4902 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4903 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4905 usick = [u for u in units
4906 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4907 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4908 usickcrr = usick["check-and-repair-results"]
4909 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4910 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4911 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4912 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4915 self.failUnlessEqual(stats["type"], "stats")
4917 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4918 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4919 self.failUnlessReallyEqual(s["count-directories"], 1)
4920 d.addCallback(_done)
4922 d.addErrback(self.explain_web_error)
4925 def _count_leases(self, ignored, which):
4926 u = self.uris[which]
4927 shares = self.find_uri_shares(u)
4929 for shnum, serverid, fn in shares:
4930 sf = get_share_file(fn)
4931 num_leases = len(list(sf.get_leases()))
4932 lease_counts.append( (fn, num_leases) )
4935 def _assert_leasecount(self, lease_counts, expected):
4936 for (fn, num_leases) in lease_counts:
4937 if num_leases != expected:
4938 self.fail("expected %d leases, have %d, on %s" %
4939 (expected, num_leases, fn))
4941 def test_add_lease(self):
4942 self.basedir = "web/Grid/add_lease"
4943 self.set_up_grid(num_clients=2)
4944 c0 = self.g.clients[0]
4947 d = c0.upload(upload.Data(DATA, convergence=""))
4948 def _stash_uri(ur, which):
4949 self.uris[which] = ur.uri
4950 d.addCallback(_stash_uri, "one")
4951 d.addCallback(lambda ign:
4952 c0.upload(upload.Data(DATA+"1", convergence="")))
4953 d.addCallback(_stash_uri, "two")
4954 def _stash_mutable_uri(n, which):
4955 self.uris[which] = n.get_uri()
4956 assert isinstance(self.uris[which], str)
4957 d.addCallback(lambda ign:
4958 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4959 d.addCallback(_stash_mutable_uri, "mutable")
4961 def _compute_fileurls(ignored):
4963 for which in self.uris:
4964 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4965 d.addCallback(_compute_fileurls)
4967 d.addCallback(self._count_leases, "one")
4968 d.addCallback(self._assert_leasecount, 1)
4969 d.addCallback(self._count_leases, "two")
4970 d.addCallback(self._assert_leasecount, 1)
4971 d.addCallback(self._count_leases, "mutable")
4972 d.addCallback(self._assert_leasecount, 1)
4974 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4975 def _got_html_good(res):
4976 self.failUnlessIn("Healthy", res)
4977 self.failIfIn("Not Healthy", res)
4978 d.addCallback(_got_html_good)
4980 d.addCallback(self._count_leases, "one")
4981 d.addCallback(self._assert_leasecount, 1)
4982 d.addCallback(self._count_leases, "two")
4983 d.addCallback(self._assert_leasecount, 1)
4984 d.addCallback(self._count_leases, "mutable")
4985 d.addCallback(self._assert_leasecount, 1)
4987 # this CHECK uses the original client, which uses the same
4988 # lease-secrets, so it will just renew the original lease
4989 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4990 d.addCallback(_got_html_good)
4992 d.addCallback(self._count_leases, "one")
4993 d.addCallback(self._assert_leasecount, 1)
4994 d.addCallback(self._count_leases, "two")
4995 d.addCallback(self._assert_leasecount, 1)
4996 d.addCallback(self._count_leases, "mutable")
4997 d.addCallback(self._assert_leasecount, 1)
4999 # this CHECK uses an alternate client, which adds a second lease
5000 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
5001 d.addCallback(_got_html_good)
5003 d.addCallback(self._count_leases, "one")
5004 d.addCallback(self._assert_leasecount, 2)
5005 d.addCallback(self._count_leases, "two")
5006 d.addCallback(self._assert_leasecount, 1)
5007 d.addCallback(self._count_leases, "mutable")
5008 d.addCallback(self._assert_leasecount, 1)
5010 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
5011 d.addCallback(_got_html_good)
5013 d.addCallback(self._count_leases, "one")
5014 d.addCallback(self._assert_leasecount, 2)
5015 d.addCallback(self._count_leases, "two")
5016 d.addCallback(self._assert_leasecount, 1)
5017 d.addCallback(self._count_leases, "mutable")
5018 d.addCallback(self._assert_leasecount, 1)
5020 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
5022 d.addCallback(_got_html_good)
5024 d.addCallback(self._count_leases, "one")
5025 d.addCallback(self._assert_leasecount, 2)
5026 d.addCallback(self._count_leases, "two")
5027 d.addCallback(self._assert_leasecount, 1)
5028 d.addCallback(self._count_leases, "mutable")
5029 d.addCallback(self._assert_leasecount, 2)
5031 d.addErrback(self.explain_web_error)
5034 def test_deep_add_lease(self):
5035 self.basedir = "web/Grid/deep_add_lease"
5036 self.set_up_grid(num_clients=2)
5037 c0 = self.g.clients[0]
5041 d = c0.create_dirnode()
5042 def _stash_root_and_create_file(n):
5044 self.uris["root"] = n.get_uri()
5045 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5046 return n.add_file(u"one", upload.Data(DATA, convergence=""))
5047 d.addCallback(_stash_root_and_create_file)
5048 def _stash_uri(fn, which):
5049 self.uris[which] = fn.get_uri()
5050 d.addCallback(_stash_uri, "one")
5051 d.addCallback(lambda ign:
5052 self.rootnode.add_file(u"small",
5053 upload.Data("literal",
5055 d.addCallback(_stash_uri, "small")
5057 d.addCallback(lambda ign:
5058 c0.create_mutable_file(publish.MutableData("mutable")))
5059 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5060 d.addCallback(_stash_uri, "mutable")
5062 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5064 units = [simplejson.loads(line)
5065 for line in res.splitlines()
5067 # root, one, small, mutable, stats
5068 self.failUnlessReallyEqual(len(units), 4+1)
5069 d.addCallback(_done)
5071 d.addCallback(self._count_leases, "root")
5072 d.addCallback(self._assert_leasecount, 1)
5073 d.addCallback(self._count_leases, "one")
5074 d.addCallback(self._assert_leasecount, 1)
5075 d.addCallback(self._count_leases, "mutable")
5076 d.addCallback(self._assert_leasecount, 1)
5078 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5079 d.addCallback(_done)
5081 d.addCallback(self._count_leases, "root")
5082 d.addCallback(self._assert_leasecount, 1)
5083 d.addCallback(self._count_leases, "one")
5084 d.addCallback(self._assert_leasecount, 1)
5085 d.addCallback(self._count_leases, "mutable")
5086 d.addCallback(self._assert_leasecount, 1)
5088 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5090 d.addCallback(_done)
5092 d.addCallback(self._count_leases, "root")
5093 d.addCallback(self._assert_leasecount, 2)
5094 d.addCallback(self._count_leases, "one")
5095 d.addCallback(self._assert_leasecount, 2)
5096 d.addCallback(self._count_leases, "mutable")
5097 d.addCallback(self._assert_leasecount, 2)
5099 d.addErrback(self.explain_web_error)
5103 def test_exceptions(self):
5104 self.basedir = "web/Grid/exceptions"
5105 self.set_up_grid(num_clients=1, num_servers=2)
5106 c0 = self.g.clients[0]
5107 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
5110 d = c0.create_dirnode()
5112 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5113 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5115 d.addCallback(_stash_root)
5116 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5118 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
5119 self.delete_shares_numbered(ur.uri, range(1,10))
5121 u = uri.from_string(ur.uri)
5122 u.key = testutil.flip_bit(u.key, 0)
5123 baduri = u.to_string()
5124 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5125 d.addCallback(_stash_bad)
5126 d.addCallback(lambda ign: c0.create_dirnode())
5127 def _mangle_dirnode_1share(n):
5129 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5130 self.fileurls["dir-1share-json"] = url + "?t=json"
5131 self.delete_shares_numbered(u, range(1,10))
5132 d.addCallback(_mangle_dirnode_1share)
5133 d.addCallback(lambda ign: c0.create_dirnode())
5134 def _mangle_dirnode_0share(n):
5136 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5137 self.fileurls["dir-0share-json"] = url + "?t=json"
5138 self.delete_shares_numbered(u, range(0,10))
5139 d.addCallback(_mangle_dirnode_0share)
5141 # NotEnoughSharesError should be reported sensibly, with a
5142 # text/plain explanation of the problem, and perhaps some
5143 # information on which shares *could* be found.
5145 d.addCallback(lambda ignored:
5146 self.shouldHTTPError("GET unrecoverable",
5147 410, "Gone", "NoSharesError",
5148 self.GET, self.fileurls["0shares"]))
5149 def _check_zero_shares(body):
5150 self.failIfIn("<html>", body)
5151 body = " ".join(body.strip().split())
5152 exp = ("NoSharesError: no shares could be found. "
5153 "Zero shares usually indicates a corrupt URI, or that "
5154 "no servers were connected, but it might also indicate "
5155 "severe corruption. You should perform a filecheck on "
5156 "this object to learn more. The full error message is: "
5157 "no shares (need 3). Last failure: None")
5158 self.failUnlessReallyEqual(exp, body)
5159 d.addCallback(_check_zero_shares)
5162 d.addCallback(lambda ignored:
5163 self.shouldHTTPError("GET 1share",
5164 410, "Gone", "NotEnoughSharesError",
5165 self.GET, self.fileurls["1share"]))
5166 def _check_one_share(body):
5167 self.failIfIn("<html>", body)
5168 body = " ".join(body.strip().split())
5169 msgbase = ("NotEnoughSharesError: This indicates that some "
5170 "servers were unavailable, or that shares have been "
5171 "lost to server departure, hard drive failure, or disk "
5172 "corruption. You should perform a filecheck on "
5173 "this object to learn more. The full error message is:"
5175 msg1 = msgbase + (" ran out of shares:"
5178 " overdue= unused= need 3. Last failure: None")
5179 msg2 = msgbase + (" ran out of shares:"
5181 " pending=Share(sh0-on-xgru5)"
5182 " overdue= unused= need 3. Last failure: None")
5183 self.failUnless(body == msg1 or body == msg2, body)
5184 d.addCallback(_check_one_share)
5186 d.addCallback(lambda ignored:
5187 self.shouldHTTPError("GET imaginary",
5188 404, "Not Found", None,
5189 self.GET, self.fileurls["imaginary"]))
5190 def _missing_child(body):
5191 self.failUnlessIn("No such child: imaginary", body)
5192 d.addCallback(_missing_child)
5194 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5195 def _check_0shares_dir_html(body):
5196 self.failUnlessIn("<html>", body)
5197 # we should see the regular page, but without the child table or
5199 body = " ".join(body.strip().split())
5200 self.failUnlessIn('href="?t=info">More info on this directory',
5202 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5203 "could not be retrieved, because there were insufficient "
5204 "good shares. This might indicate that no servers were "
5205 "connected, insufficient servers were connected, the URI "
5206 "was corrupt, or that shares have been lost due to server "
5207 "departure, hard drive failure, or disk corruption. You "
5208 "should perform a filecheck on this object to learn more.")
5209 self.failUnlessIn(exp, body)
5210 self.failUnlessIn("No upload forms: directory is unreadable", body)
5211 d.addCallback(_check_0shares_dir_html)
5213 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5214 def _check_1shares_dir_html(body):
5215 # at some point, we'll split UnrecoverableFileError into 0-shares
5216 # and some-shares like we did for immutable files (since there
5217 # are different sorts of advice to offer in each case). For now,
5218 # they present the same way.
5219 self.failUnlessIn("<html>", body)
5220 body = " ".join(body.strip().split())
5221 self.failUnlessIn('href="?t=info">More info on this directory',
5223 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5224 "could not be retrieved, because there were insufficient "
5225 "good shares. This might indicate that no servers were "
5226 "connected, insufficient servers were connected, the URI "
5227 "was corrupt, or that shares have been lost due to server "
5228 "departure, hard drive failure, or disk corruption. You "
5229 "should perform a filecheck on this object to learn more.")
5230 self.failUnlessIn(exp, body)
5231 self.failUnlessIn("No upload forms: directory is unreadable", body)
5232 d.addCallback(_check_1shares_dir_html)
5234 d.addCallback(lambda ignored:
5235 self.shouldHTTPError("GET dir-0share-json",
5236 410, "Gone", "UnrecoverableFileError",
5238 self.fileurls["dir-0share-json"]))
5239 def _check_unrecoverable_file(body):
5240 self.failIfIn("<html>", body)
5241 body = " ".join(body.strip().split())
5242 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5243 "could not be retrieved, because there were insufficient "
5244 "good shares. This might indicate that no servers were "
5245 "connected, insufficient servers were connected, the URI "
5246 "was corrupt, or that shares have been lost due to server "
5247 "departure, hard drive failure, or disk corruption. You "
5248 "should perform a filecheck on this object to learn more.")
5249 self.failUnlessReallyEqual(exp, body)
5250 d.addCallback(_check_unrecoverable_file)
5252 d.addCallback(lambda ignored:
5253 self.shouldHTTPError("GET dir-1share-json",
5254 410, "Gone", "UnrecoverableFileError",
5256 self.fileurls["dir-1share-json"]))
5257 d.addCallback(_check_unrecoverable_file)
5259 d.addCallback(lambda ignored:
5260 self.shouldHTTPError("GET imaginary",
5261 404, "Not Found", None,
5262 self.GET, self.fileurls["imaginary"]))
5264 # attach a webapi child that throws a random error, to test how it
5266 w = c0.getServiceNamed("webish")
5267 w.root.putChild("ERRORBOOM", ErrorBoom())
5269 # "Accept: */*" : should get a text/html stack trace
5270 # "Accept: text/plain" : should get a text/plain stack trace
5271 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5272 # no Accept header: should get a text/html stack trace
5274 d.addCallback(lambda ignored:
5275 self.shouldHTTPError("GET errorboom_html",
5276 500, "Internal Server Error", None,
5277 self.GET, "ERRORBOOM",
5278 headers={"accept": ["*/*"]}))
5279 def _internal_error_html1(body):
5280 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5281 d.addCallback(_internal_error_html1)
5283 d.addCallback(lambda ignored:
5284 self.shouldHTTPError("GET errorboom_text",
5285 500, "Internal Server Error", None,
5286 self.GET, "ERRORBOOM",
5287 headers={"accept": ["text/plain"]}))
5288 def _internal_error_text2(body):
5289 self.failIfIn("<html>", body)
5290 self.failUnless(body.startswith("Traceback "), body)
5291 d.addCallback(_internal_error_text2)
5293 CLI_accepts = "text/plain, application/octet-stream"
5294 d.addCallback(lambda ignored:
5295 self.shouldHTTPError("GET errorboom_text",
5296 500, "Internal Server Error", None,
5297 self.GET, "ERRORBOOM",
5298 headers={"accept": [CLI_accepts]}))
5299 def _internal_error_text3(body):
5300 self.failIfIn("<html>", body)
5301 self.failUnless(body.startswith("Traceback "), body)
5302 d.addCallback(_internal_error_text3)
5304 d.addCallback(lambda ignored:
5305 self.shouldHTTPError("GET errorboom_text",
5306 500, "Internal Server Error", None,
5307 self.GET, "ERRORBOOM"))
5308 def _internal_error_html4(body):
5309 self.failUnlessIn("<html>", body)
5310 d.addCallback(_internal_error_html4)
5312 def _flush_errors(res):
5313 # Trial: please ignore the CompletelyUnhandledError in the logs
5314 self.flushLoggedErrors(CompletelyUnhandledError)
5316 d.addBoth(_flush_errors)
5320 def test_blacklist(self):
5321 # download from a blacklisted URI, get an error
5322 self.basedir = "web/Grid/blacklist"
5324 c0 = self.g.clients[0]
5325 c0_basedir = c0.basedir
5326 fn = os.path.join(c0_basedir, "access.blacklist")
5328 DATA = "off-limits " * 50
5330 d = c0.upload(upload.Data(DATA, convergence=""))
5331 def _stash_uri_and_create_dir(ur):
5333 self.url = "uri/"+self.uri
5334 u = uri.from_string_filenode(self.uri)
5335 self.si = u.get_storage_index()
5336 childnode = c0.create_node_from_uri(self.uri, None)
5337 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5338 d.addCallback(_stash_uri_and_create_dir)
5339 def _stash_dir(node):
5340 self.dir_node = node
5341 self.dir_uri = node.get_uri()
5342 self.dir_url = "uri/"+self.dir_uri
5343 d.addCallback(_stash_dir)
5344 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5345 def _check_dir_html(body):
5346 self.failUnlessIn("<html>", body)
5347 self.failUnlessIn("blacklisted.txt</a>", body)
5348 d.addCallback(_check_dir_html)
5349 d.addCallback(lambda ign: self.GET(self.url))
5350 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5352 def _blacklist(ign):
5354 f.write(" # this is a comment\n")
5356 f.write("\n") # also exercise blank lines
5357 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5359 # clients should be checking the blacklist each time, so we don't
5360 # need to restart the client
5361 d.addCallback(_blacklist)
5362 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5364 "Access Prohibited: off-limits",
5365 self.GET, self.url))
5367 # We should still be able to list the parent directory, in HTML...
5368 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5369 def _check_dir_html2(body):
5370 self.failUnlessIn("<html>", body)
5371 self.failUnlessIn("blacklisted.txt</strike>", body)
5372 d.addCallback(_check_dir_html2)
5374 # ... and in JSON (used by CLI).
5375 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5376 def _check_dir_json(res):
5377 data = simplejson.loads(res)
5378 self.failUnless(isinstance(data, list), data)
5379 self.failUnlessEqual(data[0], "dirnode")
5380 self.failUnless(isinstance(data[1], dict), data)
5381 self.failUnlessIn("children", data[1])
5382 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5383 childdata = data[1]["children"]["blacklisted.txt"]
5384 self.failUnless(isinstance(childdata, list), data)
5385 self.failUnlessEqual(childdata[0], "filenode")
5386 self.failUnless(isinstance(childdata[1], dict), data)
5387 d.addCallback(_check_dir_json)
5389 def _unblacklist(ign):
5390 open(fn, "w").close()
5391 # the Blacklist object watches mtime to tell when the file has
5392 # changed, but on windows this test will run faster than the
5393 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5394 # to force a reload.
5395 self.g.clients[0].blacklist.last_mtime -= 2.0
5396 d.addCallback(_unblacklist)
5398 # now a read should work
5399 d.addCallback(lambda ign: self.GET(self.url))
5400 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5402 # read again to exercise the blacklist-is-unchanged logic
5403 d.addCallback(lambda ign: self.GET(self.url))
5404 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5406 # now add a blacklisted directory, and make sure files under it are
5409 childnode = c0.create_node_from_uri(self.uri, None)
5410 return c0.create_dirnode({u"child": (childnode,{}) })
5411 d.addCallback(_add_dir)
5412 def _get_dircap(dn):
5413 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5414 self.dir_url_base = "uri/"+dn.get_write_uri()
5415 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5416 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5417 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5418 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5419 d.addCallback(_get_dircap)
5420 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5421 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5422 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5423 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5424 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5425 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5426 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5427 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5428 d.addCallback(lambda ign: self.GET(self.child_url))
5429 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5431 def _block_dir(ign):
5433 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5435 self.g.clients[0].blacklist.last_mtime -= 2.0
5436 d.addCallback(_block_dir)
5437 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5439 "Access Prohibited: dir-off-limits",
5440 self.GET, self.dir_url_base))
5441 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5443 "Access Prohibited: dir-off-limits",
5444 self.GET, self.dir_url_json1))
5445 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5447 "Access Prohibited: dir-off-limits",
5448 self.GET, self.dir_url_json2))
5449 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5451 "Access Prohibited: dir-off-limits",
5452 self.GET, self.dir_url_json_ro))
5453 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5455 "Access Prohibited: dir-off-limits",
5456 self.GET, self.child_url))
5460 class CompletelyUnhandledError(Exception):
5462 class ErrorBoom(rend.Page):
5463 def beforeRender(self, ctx):
5464 raise CompletelyUnhandledError("whoops")