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 def test_GET_etags(self):
905 def _check_etags(uri):
907 d2 = _get_etag(uri, 'json')
908 d = defer.DeferredList([d1, d2], consumeErrors=True)
910 # All deferred must succeed
911 self.failUnless(all([r[0] for r in results]))
912 # the etag for the t=json form should be just like the etag
913 # fo the default t='' form, but with a 'json' suffix
914 self.failUnlessEqual(results[0][1] + 'json', results[1][1])
915 d.addCallback(_check)
918 def _get_etag(uri, t=''):
919 targetbase = "/uri/%s?t=%s" % (urllib.quote(uri.strip()), t)
920 d = self.GET(targetbase, return_response=True, followRedirect=True)
921 def _just_the_etag(result):
922 data, response, headers = result
923 etag = headers['etag'][0]
924 if uri.startswith('URI:DIR'):
925 self.failUnless(etag.startswith('DIR:'), etag)
927 return d.addCallback(_just_the_etag)
929 # Check that etags work with immutable directories
930 (newkids, caps) = self._create_immutable_children()
931 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
932 simplejson.dumps(newkids))
933 d.addCallback(_check_etags)
935 # Check that etags work with immutable files
936 d.addCallback(lambda _: _check_etags(self._bar_txt_uri))
938 # use the ETag on GET
939 def _check_match(ign):
940 uri = "/uri/%s" % self._bar_txt_uri
941 d = self.GET(uri, return_response=True)
943 d.addCallback(lambda (data, code, headers):
945 # do a GET that's supposed to match the ETag
946 d.addCallback(lambda etag:
947 self.GET(uri, return_response=True,
948 headers={"If-None-Match": etag}))
949 # make sure it short-circuited (304 instead of 200)
950 d.addCallback(lambda (data, code, headers):
951 self.failUnlessEqual(int(code), http.NOT_MODIFIED))
953 d.addCallback(_check_match)
956 # TODO: version of this with a Unicode filename
957 def test_GET_FILEURL_save(self):
958 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
959 return_response=True)
960 def _got((res, statuscode, headers)):
961 content_disposition = headers["content-disposition"][0]
962 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
963 self.failUnlessIsBarDotTxt(res)
967 def test_GET_FILEURL_missing(self):
968 d = self.GET(self.public_url + "/foo/missing")
969 d.addBoth(self.should404, "test_GET_FILEURL_missing")
972 def test_GET_FILEURL_info_mdmf(self):
973 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
975 self.failUnlessIn("mutable file (mdmf)", res)
976 self.failUnlessIn(self._quux_txt_uri, res)
977 self.failUnlessIn(self._quux_txt_readonly_uri, res)
981 def test_GET_FILEURL_info_mdmf_readonly(self):
982 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
984 self.failUnlessIn("mutable file (mdmf)", res)
985 self.failIfIn(self._quux_txt_uri, res)
986 self.failUnlessIn(self._quux_txt_readonly_uri, res)
990 def test_GET_FILEURL_info_sdmf(self):
991 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
993 self.failUnlessIn("mutable file (sdmf)", res)
994 self.failUnlessIn(self._baz_txt_uri, res)
998 def test_GET_FILEURL_info_mdmf_extensions(self):
999 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
1001 self.failUnlessIn("mutable file (mdmf)", res)
1002 self.failUnlessIn(self._quux_txt_uri, res)
1003 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1007 def test_PUT_overwrite_only_files(self):
1008 # create a directory, put a file in that directory.
1009 contents, n, filecap = self.makefile(8)
1010 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1011 d.addCallback(lambda res:
1012 self.PUT(self.public_url + "/foo/dir/file1.txt",
1013 self.NEWFILE_CONTENTS))
1014 # try to overwrite the file with replace=only-files
1015 # (this should work)
1016 d.addCallback(lambda res:
1017 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1019 d.addCallback(lambda res:
1020 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1021 "There was already a child by that name, and you asked me "
1022 "to not replace it",
1023 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
1027 def test_PUT_NEWFILEURL(self):
1028 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1029 # TODO: we lose the response code, so we can't check this
1030 #self.failUnlessReallyEqual(responsecode, 201)
1031 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1032 d.addCallback(lambda res:
1033 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1034 self.NEWFILE_CONTENTS))
1037 def test_PUT_NEWFILEURL_not_mutable(self):
1038 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1039 self.NEWFILE_CONTENTS)
1040 # TODO: we lose the response code, so we can't check this
1041 #self.failUnlessReallyEqual(responsecode, 201)
1042 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1043 d.addCallback(lambda res:
1044 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1045 self.NEWFILE_CONTENTS))
1048 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
1049 # this should get us a few segments of an MDMF mutable file,
1050 # which we can then test for.
1051 contents = self.NEWFILE_CONTENTS * 300000
1052 d = self.PUT("/uri?format=mdmf",
1054 def _got_filecap(filecap):
1055 self.failUnless(filecap.startswith("URI:MDMF"))
1057 d.addCallback(_got_filecap)
1058 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1059 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1062 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1063 contents = self.NEWFILE_CONTENTS * 300000
1064 d = self.PUT("/uri?format=sdmf",
1066 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1067 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1070 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1071 contents = self.NEWFILE_CONTENTS * 300000
1072 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1073 400, "Bad Request", "Unknown format: foo",
1074 self.PUT, "/uri?format=foo",
1077 def test_PUT_NEWFILEURL_range_bad(self):
1078 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1079 target = self.public_url + "/foo/new.txt"
1080 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1081 "501 Not Implemented",
1082 "Content-Range in PUT not yet supported",
1083 # (and certainly not for immutable files)
1084 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1086 d.addCallback(lambda res:
1087 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1090 def test_PUT_NEWFILEURL_mutable(self):
1091 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1092 self.NEWFILE_CONTENTS)
1093 # TODO: we lose the response code, so we can't check this
1094 #self.failUnlessReallyEqual(responsecode, 201)
1095 def _check_uri(res):
1096 u = uri.from_string_mutable_filenode(res)
1097 self.failUnless(u.is_mutable())
1098 self.failIf(u.is_readonly())
1100 d.addCallback(_check_uri)
1101 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1102 d.addCallback(lambda res:
1103 self.failUnlessMutableChildContentsAre(self._foo_node,
1105 self.NEWFILE_CONTENTS))
1108 def test_PUT_NEWFILEURL_mutable_toobig(self):
1109 # It is okay to upload large mutable files, so we should be able
1111 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1112 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1115 def test_PUT_NEWFILEURL_replace(self):
1116 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1117 # TODO: we lose the response code, so we can't check this
1118 #self.failUnlessReallyEqual(responsecode, 200)
1119 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1120 d.addCallback(lambda res:
1121 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1122 self.NEWFILE_CONTENTS))
1125 def test_PUT_NEWFILEURL_bad_t(self):
1126 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1127 "PUT to a file: bad t=bogus",
1128 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1132 def test_PUT_NEWFILEURL_no_replace(self):
1133 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1134 self.NEWFILE_CONTENTS)
1135 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1137 "There was already a child by that name, and you asked me "
1138 "to not replace it")
1141 def test_PUT_NEWFILEURL_mkdirs(self):
1142 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1144 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1145 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1146 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1147 d.addCallback(lambda res:
1148 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1149 self.NEWFILE_CONTENTS))
1152 def test_PUT_NEWFILEURL_blocked(self):
1153 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1154 self.NEWFILE_CONTENTS)
1155 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1157 "Unable to create directory 'blockingfile': a file was in the way")
1160 def test_PUT_NEWFILEURL_emptyname(self):
1161 # an empty pathname component (i.e. a double-slash) is disallowed
1162 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1164 "The webapi does not allow empty pathname components",
1165 self.PUT, self.public_url + "/foo//new.txt", "")
1168 def test_DELETE_FILEURL(self):
1169 d = self.DELETE(self.public_url + "/foo/bar.txt")
1170 d.addCallback(lambda res:
1171 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1174 def test_DELETE_FILEURL_missing(self):
1175 d = self.DELETE(self.public_url + "/foo/missing")
1176 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1179 def test_DELETE_FILEURL_missing2(self):
1180 d = self.DELETE(self.public_url + "/missing/missing")
1181 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1184 def failUnlessHasBarDotTxtMetadata(self, res):
1185 data = simplejson.loads(res)
1186 self.failUnless(isinstance(data, list))
1187 self.failUnlessIn("metadata", data[1])
1188 self.failUnlessIn("tahoe", data[1]["metadata"])
1189 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1190 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1191 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1192 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1194 def test_GET_FILEURL_json(self):
1195 # twisted.web.http.parse_qs ignores any query args without an '=', so
1196 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1197 # instead. This may make it tricky to emulate the S3 interface
1199 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1201 self.failUnlessIsBarJSON(data)
1202 self.failUnlessHasBarDotTxtMetadata(data)
1204 d.addCallback(_check1)
1207 def test_GET_FILEURL_json_mutable_type(self):
1208 # The JSON should include format, which says whether the
1209 # file is SDMF or MDMF
1210 d = self.PUT("/uri?format=mdmf",
1211 self.NEWFILE_CONTENTS * 300000)
1212 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1213 def _got_json(json, version):
1214 data = simplejson.loads(json)
1215 assert "filenode" == data[0]
1217 assert isinstance(data, dict)
1219 self.failUnlessIn("format", data)
1220 self.failUnlessEqual(data["format"], version)
1222 d.addCallback(_got_json, "MDMF")
1223 # Now make an SDMF file and check that it is reported correctly.
1224 d.addCallback(lambda ignored:
1225 self.PUT("/uri?format=sdmf",
1226 self.NEWFILE_CONTENTS * 300000))
1227 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1228 d.addCallback(_got_json, "SDMF")
1231 def test_GET_FILEURL_json_mdmf(self):
1232 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1233 d.addCallback(self.failUnlessIsQuuxJSON)
1236 def test_GET_FILEURL_json_missing(self):
1237 d = self.GET(self.public_url + "/foo/missing?json")
1238 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1241 def test_GET_FILEURL_uri(self):
1242 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1244 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1245 d.addCallback(_check)
1246 d.addCallback(lambda res:
1247 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1249 # for now, for files, uris and readonly-uris are the same
1250 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1251 d.addCallback(_check2)
1254 def test_GET_FILEURL_badtype(self):
1255 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1258 self.public_url + "/foo/bar.txt?t=bogus")
1261 def test_CSS_FILE(self):
1262 d = self.GET("/tahoe.css", followRedirect=True)
1264 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1265 self.failUnless(CSS_STYLE.search(res), res)
1266 d.addCallback(_check)
1269 def test_GET_FILEURL_uri_missing(self):
1270 d = self.GET(self.public_url + "/foo/missing?t=uri")
1271 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1274 def _check_upload_and_mkdir_forms(self, html):
1275 # We should have a form to create a file, with radio buttons that allow
1276 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1277 self.failUnlessIn('name="t" value="upload"', html)
1278 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1279 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1280 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1282 # We should also have the ability to create a mutable directory, with
1283 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1284 # or MDMF directory.
1285 self.failUnlessIn('name="t" value="mkdir"', html)
1286 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1287 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1289 self.failUnlessIn(FAVICON_MARKUP, html)
1291 def test_GET_DIRECTORY_html(self):
1292 d = self.GET(self.public_url + "/foo", followRedirect=True)
1294 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1295 self._check_upload_and_mkdir_forms(html)
1296 self.failUnlessIn("quux", html)
1297 d.addCallback(_check)
1300 def test_GET_root_html(self):
1302 d.addCallback(self._check_upload_and_mkdir_forms)
1305 def test_GET_DIRURL(self):
1306 # the addSlash means we get a redirect here
1307 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1309 d = self.GET(self.public_url + "/foo", followRedirect=True)
1311 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1313 # the FILE reference points to a URI, but it should end in bar.txt
1314 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1315 (ROOT, urllib.quote(self._bar_txt_uri)))
1316 get_bar = "".join([r'<td>FILE</td>',
1318 r'<a href="%s">bar.txt</a>' % bar_url,
1320 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1322 self.failUnless(re.search(get_bar, res), res)
1323 for label in ['unlink', 'rename/move']:
1324 for line in res.split("\n"):
1325 # find the line that contains the relevant button for bar.txt
1326 if ("form action" in line and
1327 ('value="%s"' % (label,)) in line and
1328 'value="bar.txt"' in line):
1329 # the form target should use a relative URL
1330 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1331 self.failUnlessIn('action="%s"' % foo_url, line)
1332 # and the when_done= should too
1333 #done_url = urllib.quote(???)
1334 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1336 # 'unlink' needs to use POST because it directly has a side effect
1337 if label == 'unlink':
1338 self.failUnlessIn('method="post"', line)
1341 self.fail("unable to find '%s bar.txt' line" % (label,))
1343 # the DIR reference just points to a URI
1344 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1345 get_sub = ((r'<td>DIR</td>')
1346 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1347 self.failUnless(re.search(get_sub, res), res)
1348 d.addCallback(_check)
1350 # look at a readonly directory
1351 d.addCallback(lambda res:
1352 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1354 self.failUnlessIn("(read-only)", res)
1355 self.failIfIn("Upload a file", res)
1356 d.addCallback(_check2)
1358 # and at a directory that contains a readonly directory
1359 d.addCallback(lambda res:
1360 self.GET(self.public_url, followRedirect=True))
1362 self.failUnless(re.search('<td>DIR-RO</td>'
1363 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1364 d.addCallback(_check3)
1366 # and an empty directory
1367 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1369 self.failUnlessIn("directory is empty", res)
1370 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)
1371 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1372 d.addCallback(_check4)
1374 # and at a literal directory
1375 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1376 d.addCallback(lambda res:
1377 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1379 self.failUnlessIn('(immutable)', res)
1380 self.failUnless(re.search('<td>FILE</td>'
1381 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1382 d.addCallback(_check5)
1385 def test_GET_DIRURL_badtype(self):
1386 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1390 self.public_url + "/foo?t=bogus")
1393 def test_GET_DIRURL_json(self):
1394 d = self.GET(self.public_url + "/foo?t=json")
1395 d.addCallback(self.failUnlessIsFooJSON)
1398 def test_GET_DIRURL_json_format(self):
1399 d = self.PUT(self.public_url + \
1400 "/foo/sdmf.txt?format=sdmf",
1401 self.NEWFILE_CONTENTS * 300000)
1402 d.addCallback(lambda ignored:
1403 self.PUT(self.public_url + \
1404 "/foo/mdmf.txt?format=mdmf",
1405 self.NEWFILE_CONTENTS * 300000))
1406 # Now we have an MDMF and SDMF file in the directory. If we GET
1407 # its JSON, we should see their encodings.
1408 d.addCallback(lambda ignored:
1409 self.GET(self.public_url + "/foo?t=json"))
1410 def _got_json(json):
1411 data = simplejson.loads(json)
1412 assert data[0] == "dirnode"
1415 kids = data['children']
1417 mdmf_data = kids['mdmf.txt'][1]
1418 self.failUnlessIn("format", mdmf_data)
1419 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1421 sdmf_data = kids['sdmf.txt'][1]
1422 self.failUnlessIn("format", sdmf_data)
1423 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1424 d.addCallback(_got_json)
1428 def test_POST_DIRURL_manifest_no_ophandle(self):
1429 d = self.shouldFail2(error.Error,
1430 "test_POST_DIRURL_manifest_no_ophandle",
1432 "slow operation requires ophandle=",
1433 self.POST, self.public_url, t="start-manifest")
1436 def test_POST_DIRURL_manifest(self):
1437 d = defer.succeed(None)
1438 def getman(ignored, output):
1439 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1440 followRedirect=True)
1441 d.addCallback(self.wait_for_operation, "125")
1442 d.addCallback(self.get_operation_results, "125", output)
1444 d.addCallback(getman, None)
1445 def _got_html(manifest):
1446 self.failUnlessIn("Manifest of SI=", manifest)
1447 self.failUnlessIn("<td>sub</td>", manifest)
1448 self.failUnlessIn(self._sub_uri, manifest)
1449 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1450 self.failUnlessIn(FAVICON_MARKUP, manifest)
1451 d.addCallback(_got_html)
1453 # both t=status and unadorned GET should be identical
1454 d.addCallback(lambda res: self.GET("/operations/125"))
1455 d.addCallback(_got_html)
1457 d.addCallback(getman, "html")
1458 d.addCallback(_got_html)
1459 d.addCallback(getman, "text")
1460 def _got_text(manifest):
1461 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1462 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1463 d.addCallback(_got_text)
1464 d.addCallback(getman, "JSON")
1466 data = res["manifest"]
1468 for (path_list, cap) in data:
1469 got[tuple(path_list)] = cap
1470 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1471 self.failUnlessIn((u"sub", u"baz.txt"), got)
1472 self.failUnlessIn("finished", res)
1473 self.failUnlessIn("origin", res)
1474 self.failUnlessIn("storage-index", res)
1475 self.failUnlessIn("verifycaps", res)
1476 self.failUnlessIn("stats", res)
1477 d.addCallback(_got_json)
1480 def test_POST_DIRURL_deepsize_no_ophandle(self):
1481 d = self.shouldFail2(error.Error,
1482 "test_POST_DIRURL_deepsize_no_ophandle",
1484 "slow operation requires ophandle=",
1485 self.POST, self.public_url, t="start-deep-size")
1488 def test_POST_DIRURL_deepsize(self):
1489 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1490 followRedirect=True)
1491 d.addCallback(self.wait_for_operation, "126")
1492 d.addCallback(self.get_operation_results, "126", "json")
1493 def _got_json(data):
1494 self.failUnlessReallyEqual(data["finished"], True)
1496 self.failUnless(size > 1000)
1497 d.addCallback(_got_json)
1498 d.addCallback(self.get_operation_results, "126", "text")
1500 mo = re.search(r'^size: (\d+)$', res, re.M)
1501 self.failUnless(mo, res)
1502 size = int(mo.group(1))
1503 # with directories, the size varies.
1504 self.failUnless(size > 1000)
1505 d.addCallback(_got_text)
1508 def test_POST_DIRURL_deepstats_no_ophandle(self):
1509 d = self.shouldFail2(error.Error,
1510 "test_POST_DIRURL_deepstats_no_ophandle",
1512 "slow operation requires ophandle=",
1513 self.POST, self.public_url, t="start-deep-stats")
1516 def test_POST_DIRURL_deepstats(self):
1517 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1518 followRedirect=True)
1519 d.addCallback(self.wait_for_operation, "127")
1520 d.addCallback(self.get_operation_results, "127", "json")
1521 def _got_json(stats):
1522 expected = {"count-immutable-files": 3,
1523 "count-mutable-files": 2,
1524 "count-literal-files": 0,
1526 "count-directories": 3,
1527 "size-immutable-files": 57,
1528 "size-literal-files": 0,
1529 #"size-directories": 1912, # varies
1530 #"largest-directory": 1590,
1531 "largest-directory-children": 7,
1532 "largest-immutable-file": 19,
1534 for k,v in expected.iteritems():
1535 self.failUnlessReallyEqual(stats[k], v,
1536 "stats[%s] was %s, not %s" %
1538 self.failUnlessReallyEqual(stats["size-files-histogram"],
1540 d.addCallback(_got_json)
1543 def test_POST_DIRURL_stream_manifest(self):
1544 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1546 self.failUnless(res.endswith("\n"))
1547 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1548 self.failUnlessReallyEqual(len(units), 9)
1549 self.failUnlessEqual(units[-1]["type"], "stats")
1551 self.failUnlessEqual(first["path"], [])
1552 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1553 self.failUnlessEqual(first["type"], "directory")
1554 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1555 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1556 self.failIfEqual(baz["storage-index"], None)
1557 self.failIfEqual(baz["verifycap"], None)
1558 self.failIfEqual(baz["repaircap"], None)
1559 # XXX: Add quux and baz to this test.
1561 d.addCallback(_check)
1564 def test_GET_DIRURL_uri(self):
1565 d = self.GET(self.public_url + "/foo?t=uri")
1567 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1568 d.addCallback(_check)
1571 def test_GET_DIRURL_readonly_uri(self):
1572 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1574 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1575 d.addCallback(_check)
1578 def test_PUT_NEWDIRURL(self):
1579 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1580 d.addCallback(lambda res:
1581 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1582 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1583 d.addCallback(self.failUnlessNodeKeysAre, [])
1586 def test_PUT_NEWDIRURL_mdmf(self):
1587 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1588 d.addCallback(lambda res:
1589 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1590 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1591 d.addCallback(lambda node:
1592 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1595 def test_PUT_NEWDIRURL_sdmf(self):
1596 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1598 d.addCallback(lambda res:
1599 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1600 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1601 d.addCallback(lambda node:
1602 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1605 def test_PUT_NEWDIRURL_bad_format(self):
1606 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1607 400, "Bad Request", "Unknown format: foo",
1608 self.PUT, self.public_url +
1609 "/foo/newdir=?t=mkdir&format=foo", "")
1611 def test_POST_NEWDIRURL(self):
1612 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1613 d.addCallback(lambda res:
1614 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1615 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1616 d.addCallback(self.failUnlessNodeKeysAre, [])
1619 def test_POST_NEWDIRURL_mdmf(self):
1620 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1621 d.addCallback(lambda res:
1622 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1623 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1624 d.addCallback(lambda node:
1625 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1628 def test_POST_NEWDIRURL_sdmf(self):
1629 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1630 d.addCallback(lambda res:
1631 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1632 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1633 d.addCallback(lambda node:
1634 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1637 def test_POST_NEWDIRURL_bad_format(self):
1638 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1639 400, "Bad Request", "Unknown format: foo",
1640 self.POST2, self.public_url + \
1641 "/foo/newdir?t=mkdir&format=foo", "")
1643 def test_POST_NEWDIRURL_emptyname(self):
1644 # an empty pathname component (i.e. a double-slash) is disallowed
1645 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1647 "The webapi does not allow empty pathname components, i.e. a double slash",
1648 self.POST, self.public_url + "//?t=mkdir")
1651 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1652 (newkids, caps) = self._create_initial_children()
1653 query = "/foo/newdir?t=mkdir-with-children"
1654 if version == MDMF_VERSION:
1655 query += "&format=mdmf"
1656 elif version == SDMF_VERSION:
1657 query += "&format=sdmf"
1659 version = SDMF_VERSION # for later
1660 d = self.POST2(self.public_url + query,
1661 simplejson.dumps(newkids))
1663 n = self.s.create_node_from_uri(uri.strip())
1664 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1665 self.failUnlessEqual(n._node.get_version(), version)
1666 d2.addCallback(lambda ign:
1667 self.failUnlessROChildURIIs(n, u"child-imm",
1669 d2.addCallback(lambda ign:
1670 self.failUnlessRWChildURIIs(n, u"child-mutable",
1672 d2.addCallback(lambda ign:
1673 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1675 d2.addCallback(lambda ign:
1676 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1677 caps['unknown_rocap']))
1678 d2.addCallback(lambda ign:
1679 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1680 caps['unknown_rwcap']))
1681 d2.addCallback(lambda ign:
1682 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1683 caps['unknown_immcap']))
1684 d2.addCallback(lambda ign:
1685 self.failUnlessRWChildURIIs(n, u"dirchild",
1687 d2.addCallback(lambda ign:
1688 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1690 d2.addCallback(lambda ign:
1691 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1692 caps['emptydircap']))
1694 d.addCallback(_check)
1695 d.addCallback(lambda res:
1696 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1697 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1698 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1699 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1700 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1703 def test_POST_NEWDIRURL_initial_children(self):
1704 return self._do_POST_NEWDIRURL_initial_children_test()
1706 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1707 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1709 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1710 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1712 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1713 (newkids, caps) = self._create_initial_children()
1714 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1715 400, "Bad Request", "Unknown format: foo",
1716 self.POST2, self.public_url + \
1717 "/foo/newdir?t=mkdir-with-children&format=foo",
1718 simplejson.dumps(newkids))
1720 def test_POST_NEWDIRURL_immutable(self):
1721 (newkids, caps) = self._create_immutable_children()
1722 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1723 simplejson.dumps(newkids))
1725 n = self.s.create_node_from_uri(uri.strip())
1726 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1727 d2.addCallback(lambda ign:
1728 self.failUnlessROChildURIIs(n, u"child-imm",
1730 d2.addCallback(lambda ign:
1731 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1732 caps['unknown_immcap']))
1733 d2.addCallback(lambda ign:
1734 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1736 d2.addCallback(lambda ign:
1737 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1739 d2.addCallback(lambda ign:
1740 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1741 caps['emptydircap']))
1743 d.addCallback(_check)
1744 d.addCallback(lambda res:
1745 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1746 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1747 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1748 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1749 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1750 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1751 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1752 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1753 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1754 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1755 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1756 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1757 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1758 d.addErrback(self.explain_web_error)
1761 def test_POST_NEWDIRURL_immutable_bad(self):
1762 (newkids, caps) = self._create_initial_children()
1763 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1765 "needed to be immutable but was not",
1767 self.public_url + "/foo/newdir?t=mkdir-immutable",
1768 simplejson.dumps(newkids))
1771 def test_PUT_NEWDIRURL_exists(self):
1772 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1773 d.addCallback(lambda res:
1774 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1775 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1776 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1779 def test_PUT_NEWDIRURL_blocked(self):
1780 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1781 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1783 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1784 d.addCallback(lambda res:
1785 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1786 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1787 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1790 def test_PUT_NEWDIRURL_mkdirs(self):
1791 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1792 d.addCallback(lambda res:
1793 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1794 d.addCallback(lambda res:
1795 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1796 d.addCallback(lambda res:
1797 self._foo_node.get_child_at_path(u"subdir/newdir"))
1798 d.addCallback(self.failUnlessNodeKeysAre, [])
1801 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1802 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1803 d.addCallback(lambda ignored:
1804 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1805 d.addCallback(lambda ignored:
1806 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1807 d.addCallback(lambda ignored:
1808 self._foo_node.get_child_at_path(u"subdir"))
1809 def _got_subdir(subdir):
1810 # XXX: What we want?
1811 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1812 self.failUnlessNodeHasChild(subdir, u"newdir")
1813 return subdir.get_child_at_path(u"newdir")
1814 d.addCallback(_got_subdir)
1815 d.addCallback(lambda newdir:
1816 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1819 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1820 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1821 d.addCallback(lambda ignored:
1822 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1823 d.addCallback(lambda ignored:
1824 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1825 d.addCallback(lambda ignored:
1826 self._foo_node.get_child_at_path(u"subdir"))
1827 def _got_subdir(subdir):
1828 # XXX: What we want?
1829 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1830 self.failUnlessNodeHasChild(subdir, u"newdir")
1831 return subdir.get_child_at_path(u"newdir")
1832 d.addCallback(_got_subdir)
1833 d.addCallback(lambda newdir:
1834 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1837 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1838 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1839 400, "Bad Request", "Unknown format: foo",
1840 self.PUT, self.public_url + \
1841 "/foo/subdir/newdir?t=mkdir&format=foo",
1844 def test_DELETE_DIRURL(self):
1845 d = self.DELETE(self.public_url + "/foo")
1846 d.addCallback(lambda res:
1847 self.failIfNodeHasChild(self.public_root, u"foo"))
1850 def test_DELETE_DIRURL_missing(self):
1851 d = self.DELETE(self.public_url + "/foo/missing")
1852 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1853 d.addCallback(lambda res:
1854 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1857 def test_DELETE_DIRURL_missing2(self):
1858 d = self.DELETE(self.public_url + "/missing")
1859 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1862 def dump_root(self):
1864 w = webish.DirnodeWalkerMixin()
1865 def visitor(childpath, childnode, metadata):
1867 d = w.walk(self.public_root, visitor)
1870 def failUnlessNodeKeysAre(self, node, expected_keys):
1871 for k in expected_keys:
1872 assert isinstance(k, unicode)
1874 def _check(children):
1875 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1876 d.addCallback(_check)
1878 def failUnlessNodeHasChild(self, node, name):
1879 assert isinstance(name, unicode)
1881 def _check(children):
1882 self.failUnlessIn(name, children)
1883 d.addCallback(_check)
1885 def failIfNodeHasChild(self, node, name):
1886 assert isinstance(name, unicode)
1888 def _check(children):
1889 self.failIfIn(name, children)
1890 d.addCallback(_check)
1893 def failUnlessChildContentsAre(self, node, name, expected_contents):
1894 assert isinstance(name, unicode)
1895 d = node.get_child_at_path(name)
1896 d.addCallback(lambda node: download_to_data(node))
1897 def _check(contents):
1898 self.failUnlessReallyEqual(contents, expected_contents)
1899 d.addCallback(_check)
1902 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1903 assert isinstance(name, unicode)
1904 d = node.get_child_at_path(name)
1905 d.addCallback(lambda node: node.download_best_version())
1906 def _check(contents):
1907 self.failUnlessReallyEqual(contents, expected_contents)
1908 d.addCallback(_check)
1911 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1912 assert isinstance(name, unicode)
1913 d = node.get_child_at_path(name)
1915 self.failUnless(child.is_unknown() or not child.is_readonly())
1916 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1917 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1918 expected_ro_uri = self._make_readonly(expected_uri)
1920 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1921 d.addCallback(_check)
1924 def failUnlessROChildURIIs(self, node, name, expected_uri):
1925 assert isinstance(name, unicode)
1926 d = node.get_child_at_path(name)
1928 self.failUnless(child.is_unknown() or child.is_readonly())
1929 self.failUnlessReallyEqual(child.get_write_uri(), None)
1930 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1931 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1932 d.addCallback(_check)
1935 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1936 assert isinstance(name, unicode)
1937 d = node.get_child_at_path(name)
1939 self.failUnless(child.is_unknown() or not child.is_readonly())
1940 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1941 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1942 expected_ro_uri = self._make_readonly(got_uri)
1944 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1945 d.addCallback(_check)
1948 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1949 assert isinstance(name, unicode)
1950 d = node.get_child_at_path(name)
1952 self.failUnless(child.is_unknown() or child.is_readonly())
1953 self.failUnlessReallyEqual(child.get_write_uri(), None)
1954 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1955 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1956 d.addCallback(_check)
1959 def failUnlessCHKURIHasContents(self, got_uri, contents):
1960 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1962 def test_POST_upload(self):
1963 d = self.POST(self.public_url + "/foo", t="upload",
1964 file=("new.txt", self.NEWFILE_CONTENTS))
1966 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1967 d.addCallback(lambda res:
1968 self.failUnlessChildContentsAre(fn, u"new.txt",
1969 self.NEWFILE_CONTENTS))
1972 def test_POST_upload_unicode(self):
1973 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1974 d = self.POST(self.public_url + "/foo", t="upload",
1975 file=(filename, self.NEWFILE_CONTENTS))
1977 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1978 d.addCallback(lambda res:
1979 self.failUnlessChildContentsAre(fn, filename,
1980 self.NEWFILE_CONTENTS))
1981 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1982 d.addCallback(lambda res: self.GET(target_url))
1983 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1984 self.NEWFILE_CONTENTS,
1988 def test_POST_upload_unicode_named(self):
1989 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1990 d = self.POST(self.public_url + "/foo", t="upload",
1992 file=("overridden", self.NEWFILE_CONTENTS))
1994 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1995 d.addCallback(lambda res:
1996 self.failUnlessChildContentsAre(fn, filename,
1997 self.NEWFILE_CONTENTS))
1998 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1999 d.addCallback(lambda res: self.GET(target_url))
2000 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2001 self.NEWFILE_CONTENTS,
2005 def test_POST_upload_no_link(self):
2006 d = self.POST("/uri", t="upload",
2007 file=("new.txt", self.NEWFILE_CONTENTS))
2008 def _check_upload_results(page):
2009 # this should be a page which describes the results of the upload
2010 # that just finished.
2011 self.failUnlessIn("Upload Results:", page)
2012 self.failUnlessIn("URI:", page)
2013 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2014 mo = uri_re.search(page)
2015 self.failUnless(mo, page)
2016 new_uri = mo.group(1)
2018 d.addCallback(_check_upload_results)
2019 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2022 def test_POST_upload_no_link_whendone(self):
2023 d = self.POST("/uri", t="upload", when_done="/",
2024 file=("new.txt", self.NEWFILE_CONTENTS))
2025 d.addBoth(self.shouldRedirect, "/")
2028 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2029 d = defer.maybeDeferred(callable, *args, **kwargs)
2031 if isinstance(res, failure.Failure):
2032 res.trap(error.PageRedirect)
2033 statuscode = res.value.status
2034 target = res.value.location
2035 return checker(statuscode, target)
2036 self.fail("%s: callable was supposed to redirect, not return '%s'"
2041 def test_POST_upload_no_link_whendone_results(self):
2042 def check(statuscode, target):
2043 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2044 self.failUnless(target.startswith(self.webish_url), target)
2045 return client.getPage(target, method="GET")
2046 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2048 self.POST, "/uri", t="upload",
2049 when_done="/uri/%(uri)s",
2050 file=("new.txt", self.NEWFILE_CONTENTS))
2051 d.addCallback(lambda res:
2052 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2055 def test_POST_upload_no_link_mutable(self):
2056 d = self.POST("/uri", t="upload", mutable="true",
2057 file=("new.txt", self.NEWFILE_CONTENTS))
2058 def _check(filecap):
2059 filecap = filecap.strip()
2060 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2061 self.filecap = filecap
2062 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2063 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
2064 n = self.s.create_node_from_uri(filecap)
2065 return n.download_best_version()
2066 d.addCallback(_check)
2068 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2069 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2070 d.addCallback(_check2)
2072 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2073 return self.GET("/file/%s" % urllib.quote(self.filecap))
2074 d.addCallback(_check3)
2076 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2077 d.addCallback(_check4)
2080 def test_POST_upload_no_link_mutable_toobig(self):
2081 # The SDMF size limit is no longer in place, so we should be
2082 # able to upload mutable files that are as large as we want them
2084 d = self.POST("/uri", t="upload", mutable="true",
2085 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2089 def test_POST_upload_format_unlinked(self):
2090 def _check_upload_unlinked(ign, format, uri_prefix):
2091 filename = format + ".txt"
2092 d = self.POST("/uri?t=upload&format=" + format,
2093 file=(filename, self.NEWFILE_CONTENTS * 300000))
2094 def _got_results(results):
2095 if format.upper() in ("SDMF", "MDMF"):
2096 # webapi.rst says this returns a filecap
2099 # for immutable, it returns an "upload results page", and
2100 # the filecap is buried inside
2101 line = [l for l in results.split("\n") if "URI: " in l][0]
2102 mo = re.search(r'<span>([^<]+)</span>', line)
2103 filecap = mo.group(1)
2104 self.failUnless(filecap.startswith(uri_prefix),
2105 (uri_prefix, filecap))
2106 return self.GET("/uri/%s?t=json" % filecap)
2107 d.addCallback(_got_results)
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)
2115 d = defer.succeed(None)
2116 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2117 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2118 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2119 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2122 def test_POST_upload_bad_format_unlinked(self):
2123 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2124 400, "Bad Request", "Unknown format: foo",
2126 "/uri?t=upload&format=foo",
2127 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2129 def test_POST_upload_format(self):
2130 def _check_upload(ign, format, uri_prefix, fn=None):
2131 filename = format + ".txt"
2132 d = self.POST(self.public_url +
2133 "/foo?t=upload&format=" + format,
2134 file=(filename, self.NEWFILE_CONTENTS * 300000))
2135 def _got_filecap(filecap):
2137 filenameu = unicode(filename)
2138 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2139 self.failUnless(filecap.startswith(uri_prefix))
2140 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2141 d.addCallback(_got_filecap)
2142 def _got_json(json):
2143 data = simplejson.loads(json)
2145 self.failUnlessIn("format", data)
2146 self.failUnlessEqual(data["format"], format.upper())
2147 d.addCallback(_got_json)
2150 d = defer.succeed(None)
2151 d.addCallback(_check_upload, "chk", "URI:CHK")
2152 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2153 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2154 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2157 def test_POST_upload_bad_format(self):
2158 return self.shouldHTTPError("POST_upload_bad_format",
2159 400, "Bad Request", "Unknown format: foo",
2160 self.POST, self.public_url + \
2161 "/foo?t=upload&format=foo",
2162 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2164 def test_POST_upload_mutable(self):
2165 # this creates a mutable file
2166 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2167 file=("new.txt", self.NEWFILE_CONTENTS))
2169 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2170 d.addCallback(lambda res:
2171 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2172 self.NEWFILE_CONTENTS))
2173 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2175 self.failUnless(IMutableFileNode.providedBy(newnode))
2176 self.failUnless(newnode.is_mutable())
2177 self.failIf(newnode.is_readonly())
2178 self._mutable_node = newnode
2179 self._mutable_uri = newnode.get_uri()
2182 # now upload it again and make sure that the URI doesn't change
2183 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2184 d.addCallback(lambda res:
2185 self.POST(self.public_url + "/foo", t="upload",
2187 file=("new.txt", NEWER_CONTENTS)))
2188 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2189 d.addCallback(lambda res:
2190 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2192 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2194 self.failUnless(IMutableFileNode.providedBy(newnode))
2195 self.failUnless(newnode.is_mutable())
2196 self.failIf(newnode.is_readonly())
2197 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2198 d.addCallback(_got2)
2200 # upload a second time, using PUT instead of POST
2201 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2202 d.addCallback(lambda res:
2203 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2204 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2205 d.addCallback(lambda res:
2206 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2209 # finally list the directory, since mutable files are displayed
2210 # slightly differently
2212 d.addCallback(lambda res:
2213 self.GET(self.public_url + "/foo/",
2214 followRedirect=True))
2215 def _check_page(res):
2216 # TODO: assert more about the contents
2217 self.failUnlessIn("SSK", res)
2219 d.addCallback(_check_page)
2221 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2223 self.failUnless(IMutableFileNode.providedBy(newnode))
2224 self.failUnless(newnode.is_mutable())
2225 self.failIf(newnode.is_readonly())
2226 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2227 d.addCallback(_got3)
2229 # look at the JSON form of the enclosing directory
2230 d.addCallback(lambda res:
2231 self.GET(self.public_url + "/foo/?t=json",
2232 followRedirect=True))
2233 def _check_page_json(res):
2234 parsed = simplejson.loads(res)
2235 self.failUnlessEqual(parsed[0], "dirnode")
2236 children = dict( [(unicode(name),value)
2238 in parsed[1]["children"].iteritems()] )
2239 self.failUnlessIn(u"new.txt", children)
2240 new_json = children[u"new.txt"]
2241 self.failUnlessEqual(new_json[0], "filenode")
2242 self.failUnless(new_json[1]["mutable"])
2243 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2244 ro_uri = self._mutable_node.get_readonly().to_string()
2245 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2246 d.addCallback(_check_page_json)
2248 # and the JSON form of the file
2249 d.addCallback(lambda res:
2250 self.GET(self.public_url + "/foo/new.txt?t=json"))
2251 def _check_file_json(res):
2252 parsed = simplejson.loads(res)
2253 self.failUnlessEqual(parsed[0], "filenode")
2254 self.failUnless(parsed[1]["mutable"])
2255 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2256 ro_uri = self._mutable_node.get_readonly().to_string()
2257 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2258 d.addCallback(_check_file_json)
2260 # and look at t=uri and t=readonly-uri
2261 d.addCallback(lambda res:
2262 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2263 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2264 d.addCallback(lambda res:
2265 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2266 def _check_ro_uri(res):
2267 ro_uri = self._mutable_node.get_readonly().to_string()
2268 self.failUnlessReallyEqual(res, ro_uri)
2269 d.addCallback(_check_ro_uri)
2271 # make sure we can get to it from /uri/URI
2272 d.addCallback(lambda res:
2273 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2274 d.addCallback(lambda res:
2275 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2277 # and that HEAD computes the size correctly
2278 d.addCallback(lambda res:
2279 self.HEAD(self.public_url + "/foo/new.txt",
2280 return_response=True))
2281 def _got_headers((res, status, headers)):
2282 self.failUnlessReallyEqual(res, "")
2283 self.failUnlessReallyEqual(headers["content-length"][0],
2284 str(len(NEW2_CONTENTS)))
2285 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2286 d.addCallback(_got_headers)
2288 # make sure that outdated size limits aren't enforced anymore.
2289 d.addCallback(lambda ignored:
2290 self.POST(self.public_url + "/foo", t="upload",
2293 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2294 d.addErrback(self.dump_error)
2297 def test_POST_upload_mutable_toobig(self):
2298 # SDMF had a size limti that was removed a while ago. MDMF has
2299 # never had a size limit. Test to make sure that we do not
2300 # encounter errors when trying to upload large mutable files,
2301 # since there should be no coded prohibitions regarding large
2303 d = self.POST(self.public_url + "/foo",
2304 t="upload", mutable="true",
2305 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2308 def dump_error(self, f):
2309 # if the web server returns an error code (like 400 Bad Request),
2310 # web.client.getPage puts the HTTP response body into the .response
2311 # attribute of the exception object that it gives back. It does not
2312 # appear in the Failure's repr(), so the ERROR that trial displays
2313 # will be rather terse and unhelpful. addErrback this method to the
2314 # end of your chain to get more information out of these errors.
2315 if f.check(error.Error):
2316 print "web.error.Error:"
2318 print f.value.response
2321 def test_POST_upload_replace(self):
2322 d = self.POST(self.public_url + "/foo", t="upload",
2323 file=("bar.txt", self.NEWFILE_CONTENTS))
2325 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2326 d.addCallback(lambda res:
2327 self.failUnlessChildContentsAre(fn, u"bar.txt",
2328 self.NEWFILE_CONTENTS))
2331 def test_POST_upload_no_replace_ok(self):
2332 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2333 file=("new.txt", self.NEWFILE_CONTENTS))
2334 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2335 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2336 self.NEWFILE_CONTENTS))
2339 def test_POST_upload_no_replace_queryarg(self):
2340 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2341 file=("bar.txt", self.NEWFILE_CONTENTS))
2342 d.addBoth(self.shouldFail, error.Error,
2343 "POST_upload_no_replace_queryarg",
2345 "There was already a child by that name, and you asked me "
2346 "to not replace it")
2347 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2348 d.addCallback(self.failUnlessIsBarDotTxt)
2351 def test_POST_upload_no_replace_field(self):
2352 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2353 file=("bar.txt", self.NEWFILE_CONTENTS))
2354 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2356 "There was already a child by that name, and you asked me "
2357 "to not replace it")
2358 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2359 d.addCallback(self.failUnlessIsBarDotTxt)
2362 def test_POST_upload_whendone(self):
2363 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2364 file=("new.txt", self.NEWFILE_CONTENTS))
2365 d.addBoth(self.shouldRedirect, "/THERE")
2367 d.addCallback(lambda res:
2368 self.failUnlessChildContentsAre(fn, u"new.txt",
2369 self.NEWFILE_CONTENTS))
2372 def test_POST_upload_named(self):
2374 d = self.POST(self.public_url + "/foo", t="upload",
2375 name="new.txt", file=self.NEWFILE_CONTENTS)
2376 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2377 d.addCallback(lambda res:
2378 self.failUnlessChildContentsAre(fn, u"new.txt",
2379 self.NEWFILE_CONTENTS))
2382 def test_POST_upload_named_badfilename(self):
2383 d = self.POST(self.public_url + "/foo", t="upload",
2384 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2385 d.addBoth(self.shouldFail, error.Error,
2386 "test_POST_upload_named_badfilename",
2388 "name= may not contain a slash",
2390 # make sure that nothing was added
2391 d.addCallback(lambda res:
2392 self.failUnlessNodeKeysAre(self._foo_node,
2393 [u"bar.txt", u"baz.txt", u"blockingfile",
2394 u"empty", u"n\u00fc.txt", u"quux.txt",
2398 def test_POST_FILEURL_check(self):
2399 bar_url = self.public_url + "/foo/bar.txt"
2400 d = self.POST(bar_url, t="check")
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",
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)
2422 d.addCallback(lambda res:
2423 self.POST(bar_url, t="check", output="JSON"))
2424 def _check_json(res):
2425 data = simplejson.loads(res)
2426 self.failUnlessIn("storage-index", data)
2427 self.failUnless(data["results"]["healthy"])
2428 d.addCallback(_check_json)
2432 def test_POST_FILEURL_check_and_repair(self):
2433 bar_url = self.public_url + "/foo/bar.txt"
2434 d = self.POST(bar_url, t="check", repair="true")
2436 self.failUnlessIn("Healthy :", res)
2437 d.addCallback(_check)
2438 redir_url = "http://allmydata.org/TARGET"
2439 def _check2(statuscode, target):
2440 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2441 self.failUnlessReallyEqual(target, redir_url)
2442 d.addCallback(lambda res:
2443 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2446 t="check", repair="true",
2447 when_done=redir_url))
2448 d.addCallback(lambda res:
2449 self.POST(bar_url, t="check", return_to=redir_url))
2451 self.failUnlessIn("Healthy :", res)
2452 self.failUnlessIn("Return to file", res)
2453 self.failUnlessIn(redir_url, res)
2454 d.addCallback(_check3)
2457 def test_POST_DIRURL_check(self):
2458 foo_url = self.public_url + "/foo/"
2459 d = self.POST(foo_url, t="check")
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",
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)
2481 d.addCallback(lambda res:
2482 self.POST(foo_url, t="check", output="JSON"))
2483 def _check_json(res):
2484 data = simplejson.loads(res)
2485 self.failUnlessIn("storage-index", data)
2486 self.failUnless(data["results"]["healthy"])
2487 d.addCallback(_check_json)
2491 def test_POST_DIRURL_check_and_repair(self):
2492 foo_url = self.public_url + "/foo/"
2493 d = self.POST(foo_url, t="check", repair="true")
2495 self.failUnlessIn("Healthy :", res)
2496 d.addCallback(_check)
2497 redir_url = "http://allmydata.org/TARGET"
2498 def _check2(statuscode, target):
2499 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2500 self.failUnlessReallyEqual(target, redir_url)
2501 d.addCallback(lambda res:
2502 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2505 t="check", repair="true",
2506 when_done=redir_url))
2507 d.addCallback(lambda res:
2508 self.POST(foo_url, t="check", return_to=redir_url))
2510 self.failUnlessIn("Healthy :", res)
2511 self.failUnlessIn("Return to file/directory", res)
2512 self.failUnlessIn(redir_url, res)
2513 d.addCallback(_check3)
2516 def test_POST_FILEURL_mdmf_check(self):
2517 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2518 d = self.POST(quux_url, t="check")
2520 self.failUnlessIn("Healthy", res)
2521 d.addCallback(_check)
2522 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2523 d.addCallback(lambda ignored:
2524 self.POST(quux_extension_url, t="check"))
2525 d.addCallback(_check)
2528 def test_POST_FILEURL_mdmf_check_and_repair(self):
2529 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2530 d = self.POST(quux_url, t="check", repair="true")
2532 self.failUnlessIn("Healthy", res)
2533 d.addCallback(_check)
2534 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2535 d.addCallback(lambda ignored:
2536 self.POST(quux_extension_url, t="check", repair="true"))
2537 d.addCallback(_check)
2540 def wait_for_operation(self, ignored, ophandle):
2541 url = "/operations/" + ophandle
2542 url += "?t=status&output=JSON"
2545 data = simplejson.loads(res)
2546 if not data["finished"]:
2547 d = self.stall(delay=1.0)
2548 d.addCallback(self.wait_for_operation, ophandle)
2554 def get_operation_results(self, ignored, ophandle, output=None):
2555 url = "/operations/" + ophandle
2558 url += "&output=" + output
2561 if output and output.lower() == "json":
2562 return simplejson.loads(res)
2567 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2568 d = self.shouldFail2(error.Error,
2569 "test_POST_DIRURL_deepcheck_no_ophandle",
2571 "slow operation requires ophandle=",
2572 self.POST, self.public_url, t="start-deep-check")
2575 def test_POST_DIRURL_deepcheck(self):
2576 def _check_redirect(statuscode, target):
2577 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2578 self.failUnless(target.endswith("/operations/123"))
2579 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2580 self.POST, self.public_url,
2581 t="start-deep-check", ophandle="123")
2582 d.addCallback(self.wait_for_operation, "123")
2583 def _check_json(data):
2584 self.failUnlessReallyEqual(data["finished"], True)
2585 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2586 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2587 d.addCallback(_check_json)
2588 d.addCallback(self.get_operation_results, "123", "html")
2589 def _check_html(res):
2590 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2591 self.failUnlessIn("Objects Healthy: <span>10</span>", res)
2592 self.failUnlessIn(FAVICON_MARKUP, res)
2593 d.addCallback(_check_html)
2595 d.addCallback(lambda res:
2596 self.GET("/operations/123/"))
2597 d.addCallback(_check_html) # should be the same as without the slash
2599 d.addCallback(lambda res:
2600 self.shouldFail2(error.Error, "one", "404 Not Found",
2601 "No detailed results for SI bogus",
2602 self.GET, "/operations/123/bogus"))
2604 foo_si = self._foo_node.get_storage_index()
2605 foo_si_s = base32.b2a(foo_si)
2606 d.addCallback(lambda res:
2607 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2608 def _check_foo_json(res):
2609 data = simplejson.loads(res)
2610 self.failUnlessEqual(data["storage-index"], foo_si_s)
2611 self.failUnless(data["results"]["healthy"])
2612 d.addCallback(_check_foo_json)
2615 def test_POST_DIRURL_deepcheck_and_repair(self):
2616 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2617 ophandle="124", output="json", followRedirect=True)
2618 d.addCallback(self.wait_for_operation, "124")
2619 def _check_json(data):
2620 self.failUnlessReallyEqual(data["finished"], True)
2621 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2622 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2623 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2624 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2625 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2626 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2627 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2628 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2629 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2630 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2631 d.addCallback(_check_json)
2632 d.addCallback(self.get_operation_results, "124", "html")
2633 def _check_html(res):
2634 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2636 self.failUnlessIn("Objects Healthy (before repair): <span>10</span>", res)
2637 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2638 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2640 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2641 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2642 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2644 self.failUnlessIn("Objects Healthy (after repair): <span>10</span>", res)
2645 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2646 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2648 self.failUnlessIn(FAVICON_MARKUP, res)
2649 d.addCallback(_check_html)
2652 def test_POST_FILEURL_bad_t(self):
2653 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2654 "POST to file: bad t=bogus",
2655 self.POST, self.public_url + "/foo/bar.txt",
2659 def test_POST_mkdir(self): # return value?
2660 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2661 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2662 d.addCallback(self.failUnlessNodeKeysAre, [])
2665 def test_POST_mkdir_mdmf(self):
2666 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2667 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2668 d.addCallback(lambda node:
2669 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2672 def test_POST_mkdir_sdmf(self):
2673 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2674 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2675 d.addCallback(lambda node:
2676 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2679 def test_POST_mkdir_bad_format(self):
2680 return self.shouldHTTPError("POST_mkdir_bad_format",
2681 400, "Bad Request", "Unknown format: foo",
2682 self.POST, self.public_url +
2683 "/foo?t=mkdir&name=newdir&format=foo")
2685 def test_POST_mkdir_initial_children(self):
2686 (newkids, caps) = self._create_initial_children()
2687 d = self.POST2(self.public_url +
2688 "/foo?t=mkdir-with-children&name=newdir",
2689 simplejson.dumps(newkids))
2690 d.addCallback(lambda res:
2691 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2692 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2693 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2694 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2695 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2698 def test_POST_mkdir_initial_children_mdmf(self):
2699 (newkids, caps) = self._create_initial_children()
2700 d = self.POST2(self.public_url +
2701 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2702 simplejson.dumps(newkids))
2703 d.addCallback(lambda res:
2704 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2705 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2706 d.addCallback(lambda node:
2707 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2708 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2709 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2714 def test_POST_mkdir_initial_children_sdmf(self):
2715 (newkids, caps) = self._create_initial_children()
2716 d = self.POST2(self.public_url +
2717 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2718 simplejson.dumps(newkids))
2719 d.addCallback(lambda res:
2720 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2721 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2722 d.addCallback(lambda node:
2723 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2724 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2725 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2729 def test_POST_mkdir_initial_children_bad_format(self):
2730 (newkids, caps) = self._create_initial_children()
2731 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2732 400, "Bad Request", "Unknown format: foo",
2733 self.POST, self.public_url + \
2734 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2735 simplejson.dumps(newkids))
2737 def test_POST_mkdir_immutable(self):
2738 (newkids, caps) = self._create_immutable_children()
2739 d = self.POST2(self.public_url +
2740 "/foo?t=mkdir-immutable&name=newdir",
2741 simplejson.dumps(newkids))
2742 d.addCallback(lambda res:
2743 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2744 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2745 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2746 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2747 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2748 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2749 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2750 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2751 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2752 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2753 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2754 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2755 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2758 def test_POST_mkdir_immutable_bad(self):
2759 (newkids, caps) = self._create_initial_children()
2760 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2762 "needed to be immutable but was not",
2765 "/foo?t=mkdir-immutable&name=newdir",
2766 simplejson.dumps(newkids))
2769 def test_POST_mkdir_2(self):
2770 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2771 d.addCallback(lambda res:
2772 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2773 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2774 d.addCallback(self.failUnlessNodeKeysAre, [])
2777 def test_POST_mkdirs_2(self):
2778 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2779 d.addCallback(lambda res:
2780 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2781 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2782 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2783 d.addCallback(self.failUnlessNodeKeysAre, [])
2786 def test_POST_mkdir_no_parentdir_noredirect(self):
2787 d = self.POST("/uri?t=mkdir")
2788 def _after_mkdir(res):
2789 uri.DirectoryURI.init_from_string(res)
2790 d.addCallback(_after_mkdir)
2793 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2794 d = self.POST("/uri?t=mkdir&format=mdmf")
2795 def _after_mkdir(res):
2796 u = uri.from_string(res)
2797 # Check that this is an MDMF writecap
2798 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2799 d.addCallback(_after_mkdir)
2802 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2803 d = self.POST("/uri?t=mkdir&format=sdmf")
2804 def _after_mkdir(res):
2805 u = uri.from_string(res)
2806 self.failUnlessIsInstance(u, uri.DirectoryURI)
2807 d.addCallback(_after_mkdir)
2810 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2811 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2812 400, "Bad Request", "Unknown format: foo",
2813 self.POST, self.public_url +
2814 "/uri?t=mkdir&format=foo")
2816 def test_POST_mkdir_no_parentdir_noredirect2(self):
2817 # make sure form-based arguments (as on the welcome page) still work
2818 d = self.POST("/uri", t="mkdir")
2819 def _after_mkdir(res):
2820 uri.DirectoryURI.init_from_string(res)
2821 d.addCallback(_after_mkdir)
2822 d.addErrback(self.explain_web_error)
2825 def test_POST_mkdir_no_parentdir_redirect(self):
2826 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2827 d.addBoth(self.shouldRedirect, None, statuscode='303')
2828 def _check_target(target):
2829 target = urllib.unquote(target)
2830 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2831 d.addCallback(_check_target)
2834 def test_POST_mkdir_no_parentdir_redirect2(self):
2835 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2836 d.addBoth(self.shouldRedirect, None, statuscode='303')
2837 def _check_target(target):
2838 target = urllib.unquote(target)
2839 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2840 d.addCallback(_check_target)
2841 d.addErrback(self.explain_web_error)
2844 def _make_readonly(self, u):
2845 ro_uri = uri.from_string(u).get_readonly()
2848 return ro_uri.to_string()
2850 def _create_initial_children(self):
2851 contents, n, filecap1 = self.makefile(12)
2852 md1 = {"metakey1": "metavalue1"}
2853 filecap2 = make_mutable_file_uri()
2854 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2855 filecap3 = node3.get_readonly_uri()
2856 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2857 dircap = DirectoryNode(node4, None, None).get_uri()
2858 mdmfcap = make_mutable_file_uri(mdmf=True)
2859 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2860 emptydircap = "URI:DIR2-LIT:"
2861 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2862 "ro_uri": self._make_readonly(filecap1),
2863 "metadata": md1, }],
2864 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2865 "ro_uri": self._make_readonly(filecap2)}],
2866 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2867 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2868 "ro_uri": unknown_rocap}],
2869 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2870 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2871 u"dirchild": ["dirnode", {"rw_uri": dircap,
2872 "ro_uri": self._make_readonly(dircap)}],
2873 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2874 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2875 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2876 "ro_uri": self._make_readonly(mdmfcap)}],
2878 return newkids, {'filecap1': filecap1,
2879 'filecap2': filecap2,
2880 'filecap3': filecap3,
2881 'unknown_rwcap': unknown_rwcap,
2882 'unknown_rocap': unknown_rocap,
2883 'unknown_immcap': unknown_immcap,
2885 'litdircap': litdircap,
2886 'emptydircap': emptydircap,
2889 def _create_immutable_children(self):
2890 contents, n, filecap1 = self.makefile(12)
2891 md1 = {"metakey1": "metavalue1"}
2892 tnode = create_chk_filenode("immutable directory contents\n"*10)
2893 dnode = DirectoryNode(tnode, None, None)
2894 assert not dnode.is_mutable()
2895 immdircap = dnode.get_uri()
2896 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2897 emptydircap = "URI:DIR2-LIT:"
2898 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2899 "metadata": md1, }],
2900 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2901 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2902 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2903 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2905 return newkids, {'filecap1': filecap1,
2906 'unknown_immcap': unknown_immcap,
2907 'immdircap': immdircap,
2908 'litdircap': litdircap,
2909 'emptydircap': emptydircap}
2911 def test_POST_mkdir_no_parentdir_initial_children(self):
2912 (newkids, caps) = self._create_initial_children()
2913 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2914 def _after_mkdir(res):
2915 self.failUnless(res.startswith("URI:DIR"), res)
2916 n = self.s.create_node_from_uri(res)
2917 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2918 d2.addCallback(lambda ign:
2919 self.failUnlessROChildURIIs(n, u"child-imm",
2921 d2.addCallback(lambda ign:
2922 self.failUnlessRWChildURIIs(n, u"child-mutable",
2924 d2.addCallback(lambda ign:
2925 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2927 d2.addCallback(lambda ign:
2928 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2929 caps['unknown_rwcap']))
2930 d2.addCallback(lambda ign:
2931 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2932 caps['unknown_rocap']))
2933 d2.addCallback(lambda ign:
2934 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2935 caps['unknown_immcap']))
2936 d2.addCallback(lambda ign:
2937 self.failUnlessRWChildURIIs(n, u"dirchild",
2940 d.addCallback(_after_mkdir)
2943 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2944 # the regular /uri?t=mkdir operation is specified to ignore its body.
2945 # Only t=mkdir-with-children pays attention to it.
2946 (newkids, caps) = self._create_initial_children()
2947 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
2949 "t=mkdir does not accept children=, "
2950 "try t=mkdir-with-children instead",
2951 self.POST2, "/uri?t=mkdir", # without children
2952 simplejson.dumps(newkids))
2955 def test_POST_noparent_bad(self):
2956 d = self.shouldHTTPError("POST_noparent_bad",
2958 "/uri accepts only PUT, PUT?t=mkdir, "
2959 "POST?t=upload, and POST?t=mkdir",
2960 self.POST, "/uri?t=bogus")
2963 def test_POST_mkdir_no_parentdir_immutable(self):
2964 (newkids, caps) = self._create_immutable_children()
2965 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2966 def _after_mkdir(res):
2967 self.failUnless(res.startswith("URI:DIR"), res)
2968 n = self.s.create_node_from_uri(res)
2969 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2970 d2.addCallback(lambda ign:
2971 self.failUnlessROChildURIIs(n, u"child-imm",
2973 d2.addCallback(lambda ign:
2974 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2975 caps['unknown_immcap']))
2976 d2.addCallback(lambda ign:
2977 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2979 d2.addCallback(lambda ign:
2980 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2982 d2.addCallback(lambda ign:
2983 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2984 caps['emptydircap']))
2986 d.addCallback(_after_mkdir)
2989 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2990 (newkids, caps) = self._create_initial_children()
2991 d = self.shouldFail2(error.Error,
2992 "test_POST_mkdir_no_parentdir_immutable_bad",
2994 "needed to be immutable but was not",
2996 "/uri?t=mkdir-immutable",
2997 simplejson.dumps(newkids))
3000 def test_welcome_page_mkdir_button(self):
3001 # Fetch the welcome page.
3003 def _after_get_welcome_page(res):
3004 MKDIR_BUTTON_RE = re.compile(
3005 '<form action="([^"]*)" method="post".*?'
3006 '<input type="hidden" name="t" value="([^"]*)" />'
3007 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3008 '<input type="submit" value="Create a directory" />',
3010 mo = MKDIR_BUTTON_RE.search(res)
3011 formaction = mo.group(1)
3013 formaname = mo.group(3)
3014 formavalue = mo.group(4)
3015 return (formaction, formt, formaname, formavalue)
3016 d.addCallback(_after_get_welcome_page)
3017 def _after_parse_form(res):
3018 (formaction, formt, formaname, formavalue) = res
3019 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3020 d.addCallback(_after_parse_form)
3021 d.addBoth(self.shouldRedirect, None, statuscode='303')
3024 def test_POST_mkdir_replace(self): # return value?
3025 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3026 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3027 d.addCallback(self.failUnlessNodeKeysAre, [])
3030 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3031 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3032 d.addBoth(self.shouldFail, error.Error,
3033 "POST_mkdir_no_replace_queryarg",
3035 "There was already a child by that name, and you asked me "
3036 "to not replace it")
3037 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3038 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3041 def test_POST_mkdir_no_replace_field(self): # return value?
3042 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3044 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3046 "There was already a child by that name, and you asked me "
3047 "to not replace it")
3048 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3049 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3052 def test_POST_mkdir_whendone_field(self):
3053 d = self.POST(self.public_url + "/foo",
3054 t="mkdir", name="newdir", when_done="/THERE")
3055 d.addBoth(self.shouldRedirect, "/THERE")
3056 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3057 d.addCallback(self.failUnlessNodeKeysAre, [])
3060 def test_POST_mkdir_whendone_queryarg(self):
3061 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3062 t="mkdir", name="newdir")
3063 d.addBoth(self.shouldRedirect, "/THERE")
3064 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3065 d.addCallback(self.failUnlessNodeKeysAre, [])
3068 def test_POST_bad_t(self):
3069 d = self.shouldFail2(error.Error, "POST_bad_t",
3071 "POST to a directory with bad t=BOGUS",
3072 self.POST, self.public_url + "/foo", t="BOGUS")
3075 def test_POST_set_children(self, command_name="set_children"):
3076 contents9, n9, newuri9 = self.makefile(9)
3077 contents10, n10, newuri10 = self.makefile(10)
3078 contents11, n11, newuri11 = self.makefile(11)
3081 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3084 "ctime": 1002777696.7564139,
3085 "mtime": 1002777696.7564139
3088 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3091 "ctime": 1002777696.7564139,
3092 "mtime": 1002777696.7564139
3095 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3098 "ctime": 1002777696.7564139,
3099 "mtime": 1002777696.7564139
3102 }""" % (newuri9, newuri10, newuri11)
3104 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3106 d = client.getPage(url, method="POST", postdata=reqbody)
3108 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3109 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3110 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3112 d.addCallback(_then)
3113 d.addErrback(self.dump_error)
3116 def test_POST_set_children_with_hyphen(self):
3117 return self.test_POST_set_children(command_name="set-children")
3119 def test_POST_link_uri(self):
3120 contents, n, newuri = self.makefile(8)
3121 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3122 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3123 d.addCallback(lambda res:
3124 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3128 def test_POST_link_uri_replace(self):
3129 contents, n, newuri = self.makefile(8)
3130 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3131 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3132 d.addCallback(lambda res:
3133 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3137 def test_POST_link_uri_unknown_bad(self):
3138 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3139 d.addBoth(self.shouldFail, error.Error,
3140 "POST_link_uri_unknown_bad",
3142 "unknown cap in a write slot")
3145 def test_POST_link_uri_unknown_ro_good(self):
3146 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3147 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3150 def test_POST_link_uri_unknown_imm_good(self):
3151 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3152 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3155 def test_POST_link_uri_no_replace_queryarg(self):
3156 contents, n, newuri = self.makefile(8)
3157 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3158 name="bar.txt", uri=newuri)
3159 d.addBoth(self.shouldFail, error.Error,
3160 "POST_link_uri_no_replace_queryarg",
3162 "There was already a child by that name, and you asked me "
3163 "to not replace it")
3164 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3165 d.addCallback(self.failUnlessIsBarDotTxt)
3168 def test_POST_link_uri_no_replace_field(self):
3169 contents, n, newuri = self.makefile(8)
3170 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3171 name="bar.txt", uri=newuri)
3172 d.addBoth(self.shouldFail, error.Error,
3173 "POST_link_uri_no_replace_field",
3175 "There was already a child by that name, and you asked me "
3176 "to not replace it")
3177 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3178 d.addCallback(self.failUnlessIsBarDotTxt)
3181 def test_POST_delete(self, command_name='delete'):
3182 d = self._foo_node.list()
3183 def _check_before(children):
3184 self.failUnlessIn(u"bar.txt", children)
3185 d.addCallback(_check_before)
3186 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3187 d.addCallback(lambda res: self._foo_node.list())
3188 def _check_after(children):
3189 self.failIfIn(u"bar.txt", children)
3190 d.addCallback(_check_after)
3193 def test_POST_unlink(self):
3194 return self.test_POST_delete(command_name='unlink')
3196 def test_POST_rename_file(self):
3197 d = self.POST(self.public_url + "/foo", t="rename",
3198 from_name="bar.txt", to_name='wibble.txt')
3199 d.addCallback(lambda res:
3200 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3201 d.addCallback(lambda res:
3202 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3203 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3204 d.addCallback(self.failUnlessIsBarDotTxt)
3205 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3206 d.addCallback(self.failUnlessIsBarJSON)
3209 def test_POST_rename_file_redundant(self):
3210 d = self.POST(self.public_url + "/foo", t="rename",
3211 from_name="bar.txt", to_name='bar.txt')
3212 d.addCallback(lambda res:
3213 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3214 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3215 d.addCallback(self.failUnlessIsBarDotTxt)
3216 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3217 d.addCallback(self.failUnlessIsBarJSON)
3220 def test_POST_rename_file_replace(self):
3221 # rename a file and replace a directory with it
3222 d = self.POST(self.public_url + "/foo", t="rename",
3223 from_name="bar.txt", to_name='empty')
3224 d.addCallback(lambda res:
3225 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3226 d.addCallback(lambda res:
3227 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3228 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3229 d.addCallback(self.failUnlessIsBarDotTxt)
3230 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3231 d.addCallback(self.failUnlessIsBarJSON)
3234 def test_POST_rename_file_no_replace_queryarg(self):
3235 # rename a file and replace a directory with it
3236 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3237 from_name="bar.txt", to_name='empty')
3238 d.addBoth(self.shouldFail, error.Error,
3239 "POST_rename_file_no_replace_queryarg",
3241 "There was already a child by that name, and you asked me "
3242 "to not replace it")
3243 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3244 d.addCallback(self.failUnlessIsEmptyJSON)
3247 def test_POST_rename_file_no_replace_field(self):
3248 # rename a file and replace a directory with it
3249 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3250 from_name="bar.txt", to_name='empty')
3251 d.addBoth(self.shouldFail, error.Error,
3252 "POST_rename_file_no_replace_field",
3254 "There was already a child by that name, and you asked me "
3255 "to not replace it")
3256 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3257 d.addCallback(self.failUnlessIsEmptyJSON)
3260 def failUnlessIsEmptyJSON(self, res):
3261 data = simplejson.loads(res)
3262 self.failUnlessEqual(data[0], "dirnode", data)
3263 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3265 def test_POST_rename_file_slash_fail(self):
3266 d = self.POST(self.public_url + "/foo", t="rename",
3267 from_name="bar.txt", to_name='kirk/spock.txt')
3268 d.addBoth(self.shouldFail, error.Error,
3269 "test_POST_rename_file_slash_fail",
3271 "to_name= may not contain a slash",
3273 d.addCallback(lambda res:
3274 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3277 def test_POST_rename_dir(self):
3278 d = self.POST(self.public_url, t="rename",
3279 from_name="foo", to_name='plunk')
3280 d.addCallback(lambda res:
3281 self.failIfNodeHasChild(self.public_root, u"foo"))
3282 d.addCallback(lambda res:
3283 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3284 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3285 d.addCallback(self.failUnlessIsFooJSON)
3288 def test_POST_move_file(self):
3289 d = self.POST(self.public_url + "/foo", t="move",
3290 from_name="bar.txt", to_dir="sub")
3291 d.addCallback(lambda res:
3292 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3293 d.addCallback(lambda res:
3294 self.failUnlessNodeHasChild(self._sub_node, u"bar.txt"))
3295 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3296 d.addCallback(self.failUnlessIsBarDotTxt)
3297 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3298 d.addCallback(self.failUnlessIsBarJSON)
3301 def test_POST_move_file_new_name(self):
3302 d = self.POST(self.public_url + "/foo", t="move",
3303 from_name="bar.txt", to_name="wibble.txt", to_dir="sub")
3304 d.addCallback(lambda res:
3305 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3306 d.addCallback(lambda res:
3307 self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3308 d.addCallback(lambda res:
3309 self.failUnlessNodeHasChild(self._sub_node, u"wibble.txt"))
3310 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt"))
3311 d.addCallback(self.failUnlessIsBarDotTxt)
3312 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt?t=json"))
3313 d.addCallback(self.failUnlessIsBarJSON)
3316 def test_POST_move_file_replace(self):
3317 d = self.POST(self.public_url + "/foo", t="move",
3318 from_name="bar.txt", to_name="baz.txt", to_dir="sub")
3319 d.addCallback(lambda res:
3320 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3321 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3322 d.addCallback(self.failUnlessIsBarDotTxt)
3323 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3324 d.addCallback(self.failUnlessIsBarJSON)
3327 def test_POST_move_file_no_replace(self):
3328 d = self.shouldFail2(error.Error, "POST_move_file_no_replace",
3330 "There was already a child by that name, and you asked me to not replace it",
3331 self.POST, self.public_url + "/foo", t="move",
3332 replace="false", from_name="bar.txt",
3333 to_name="baz.txt", to_dir="sub")
3334 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3335 d.addCallback(self.failUnlessIsBarDotTxt)
3336 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3337 d.addCallback(self.failUnlessIsBarJSON)
3338 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3339 d.addCallback(self.failUnlessIsSubBazDotTxt)
3342 def test_POST_move_file_slash_fail(self):
3343 d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
3345 "to_name= may not contain a slash",
3346 self.POST, self.public_url + "/foo", t="move",
3347 from_name="bar.txt",
3348 to_name="slash/fail.txt", to_dir="sub")
3349 d.addCallback(lambda res:
3350 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3351 d.addCallback(lambda res:
3352 self.failIfNodeHasChild(self._sub_node, u"slash/fail.txt"))
3353 d.addCallback(lambda ign:
3354 self.shouldFail2(error.Error,
3355 "test_POST_rename_file_slash_fail2",
3357 "from_name= may not contain a slash",
3358 self.POST, self.public_url + "/foo",
3360 from_name="nope/bar.txt",
3361 to_name="fail.txt", to_dir="sub"))
3364 def test_POST_move_file_no_target(self):
3365 d = self.shouldFail2(error.Error, "POST_move_file_no_target",
3367 "move requires from_name and to_dir",
3368 self.POST, self.public_url + "/foo", t="move",
3369 from_name="bar.txt", to_name="baz.txt")
3370 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3371 d.addCallback(self.failUnlessIsBarDotTxt)
3372 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3373 d.addCallback(self.failUnlessIsBarJSON)
3374 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3375 d.addCallback(self.failUnlessIsBazDotTxt)
3378 def test_POST_move_file_bad_target_type(self):
3379 d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type",
3380 "400 Bad Request", "invalid target_type parameter",
3382 self.public_url + "/foo", t="move",
3383 target_type="*D", from_name="bar.txt",
3387 def test_POST_move_file_multi_level(self):
3388 d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
3389 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move",
3390 from_name="bar.txt", to_dir="sub/level2"))
3391 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3392 d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3393 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
3394 d.addCallback(self.failUnlessIsBarDotTxt)
3395 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt?t=json"))
3396 d.addCallback(self.failUnlessIsBarJSON)
3399 def test_POST_move_file_to_uri(self):
3400 d = self.POST(self.public_url + "/foo", t="move", target_type="uri",
3401 from_name="bar.txt", to_dir=self._sub_uri)
3402 d.addCallback(lambda res:
3403 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3404 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3405 d.addCallback(self.failUnlessIsBarDotTxt)
3406 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3407 d.addCallback(self.failUnlessIsBarJSON)
3410 def test_POST_move_file_to_nonexist_dir(self):
3411 d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir",
3412 "404 Not Found", "No such child: nopechucktesta",
3413 self.POST, self.public_url + "/foo", t="move",
3414 from_name="bar.txt", to_dir="nopechucktesta")
3417 def test_POST_move_file_into_file(self):
3418 d = self.shouldFail2(error.Error, "POST_move_file_into_file",
3419 "400 Bad Request", "to_dir is not a directory",
3420 self.POST, self.public_url + "/foo", t="move",
3421 from_name="bar.txt", to_dir="baz.txt")
3422 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3423 d.addCallback(self.failUnlessIsBazDotTxt)
3424 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3425 d.addCallback(self.failUnlessIsBarDotTxt)
3426 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3427 d.addCallback(self.failUnlessIsBarJSON)
3430 def test_POST_move_file_to_bad_uri(self):
3431 d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri",
3432 "400 Bad Request", "to_dir is not a directory",
3433 self.POST, self.public_url + "/foo", t="move",
3434 from_name="bar.txt", target_type="uri",
3435 to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
3436 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3437 d.addCallback(self.failUnlessIsBarDotTxt)
3438 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3439 d.addCallback(self.failUnlessIsBarJSON)
3442 def test_POST_move_dir(self):
3443 d = self.POST(self.public_url + "/foo", t="move",
3444 from_name="bar.txt", to_dir="empty")
3445 d.addCallback(lambda res: self.POST(self.public_url + "/foo",
3446 t="move", from_name="empty", to_dir="sub"))
3447 d.addCallback(lambda res:
3448 self.failIfNodeHasChild(self._foo_node, u"empty"))
3449 d.addCallback(lambda res:
3450 self.failUnlessNodeHasChild(self._sub_node, u"empty"))
3451 d.addCallback(lambda res:
3452 self._sub_node.get_child_at_path(u"empty"))
3453 d.addCallback(lambda node:
3454 self.failUnlessNodeHasChild(node, u"bar.txt"))
3455 d.addCallback(lambda res:
3456 self.GET(self.public_url + "/foo/sub/empty/bar.txt"))
3457 d.addCallback(self.failUnlessIsBarDotTxt)
3460 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3461 """ If target is not None then the redirection has to go to target. If
3462 statuscode is not None then the redirection has to be accomplished with
3463 that HTTP status code."""
3464 if not isinstance(res, failure.Failure):
3465 to_where = (target is None) and "somewhere" or ("to " + target)
3466 self.fail("%s: we were expecting to get redirected %s, not get an"
3467 " actual page: %s" % (which, to_where, res))
3468 res.trap(error.PageRedirect)
3469 if statuscode is not None:
3470 self.failUnlessReallyEqual(res.value.status, statuscode,
3471 "%s: not a redirect" % which)
3472 if target is not None:
3473 # the PageRedirect does not seem to capture the uri= query arg
3474 # properly, so we can't check for it.
3475 realtarget = self.webish_url + target
3476 self.failUnlessReallyEqual(res.value.location, realtarget,
3477 "%s: wrong target" % which)
3478 return res.value.location
3480 def test_GET_URI_form(self):
3481 base = "/uri?uri=%s" % self._bar_txt_uri
3482 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3483 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3485 d.addBoth(self.shouldRedirect, targetbase)
3486 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3487 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3488 d.addCallback(lambda res: self.GET(base+"&t=json"))
3489 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3490 d.addCallback(self.log, "about to get file by uri")
3491 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3492 d.addCallback(self.failUnlessIsBarDotTxt)
3493 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3494 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3495 followRedirect=True))
3496 d.addCallback(self.failUnlessIsFooJSON)
3497 d.addCallback(self.log, "got dir by uri")
3501 def test_GET_URI_form_bad(self):
3502 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3503 "400 Bad Request", "GET /uri requires uri=",
3507 def test_GET_rename_form(self):
3508 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3509 followRedirect=True)
3511 self.failUnlessIn('name="when_done" value="."', res)
3512 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3513 self.failUnlessIn(FAVICON_MARKUP, res)
3514 d.addCallback(_check)
3517 def log(self, res, msg):
3518 #print "MSG: %s RES: %s" % (msg, res)
3522 def test_GET_URI_URL(self):
3523 base = "/uri/%s" % self._bar_txt_uri
3525 d.addCallback(self.failUnlessIsBarDotTxt)
3526 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3527 d.addCallback(self.failUnlessIsBarDotTxt)
3528 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3529 d.addCallback(self.failUnlessIsBarDotTxt)
3532 def test_GET_URI_URL_dir(self):
3533 base = "/uri/%s?t=json" % self._foo_uri
3535 d.addCallback(self.failUnlessIsFooJSON)
3538 def test_GET_URI_URL_missing(self):
3539 base = "/uri/%s" % self._bad_file_uri
3540 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3541 http.GONE, None, "NotEnoughSharesError",
3543 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3544 # here? we must arrange for a download to fail after target.open()
3545 # has been called, and then inspect the response to see that it is
3546 # shorter than we expected.
3549 def test_PUT_DIRURL_uri(self):
3550 d = self.s.create_dirnode()
3552 new_uri = dn.get_uri()
3553 # replace /foo with a new (empty) directory
3554 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3555 d.addCallback(lambda res:
3556 self.failUnlessReallyEqual(res.strip(), new_uri))
3557 d.addCallback(lambda res:
3558 self.failUnlessRWChildURIIs(self.public_root,
3562 d.addCallback(_made_dir)
3565 def test_PUT_DIRURL_uri_noreplace(self):
3566 d = self.s.create_dirnode()
3568 new_uri = dn.get_uri()
3569 # replace /foo with a new (empty) directory, but ask that
3570 # replace=false, so it should fail
3571 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3572 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3574 self.public_url + "/foo?t=uri&replace=false",
3576 d.addCallback(lambda res:
3577 self.failUnlessRWChildURIIs(self.public_root,
3581 d.addCallback(_made_dir)
3584 def test_PUT_DIRURL_bad_t(self):
3585 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3586 "400 Bad Request", "PUT to a directory",
3587 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3588 d.addCallback(lambda res:
3589 self.failUnlessRWChildURIIs(self.public_root,
3594 def test_PUT_NEWFILEURL_uri(self):
3595 contents, n, new_uri = self.makefile(8)
3596 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3597 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3598 d.addCallback(lambda res:
3599 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3603 def test_PUT_NEWFILEURL_mdmf(self):
3604 new_contents = self.NEWFILE_CONTENTS * 300000
3605 d = self.PUT(self.public_url + \
3606 "/foo/mdmf.txt?format=mdmf",
3608 d.addCallback(lambda ignored:
3609 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3610 def _got_json(json):
3611 data = simplejson.loads(json)
3613 self.failUnlessIn("format", data)
3614 self.failUnlessEqual(data["format"], "MDMF")
3615 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3616 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3617 d.addCallback(_got_json)
3620 def test_PUT_NEWFILEURL_sdmf(self):
3621 new_contents = self.NEWFILE_CONTENTS * 300000
3622 d = self.PUT(self.public_url + \
3623 "/foo/sdmf.txt?format=sdmf",
3625 d.addCallback(lambda ignored:
3626 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3627 def _got_json(json):
3628 data = simplejson.loads(json)
3630 self.failUnlessIn("format", data)
3631 self.failUnlessEqual(data["format"], "SDMF")
3632 d.addCallback(_got_json)
3635 def test_PUT_NEWFILEURL_bad_format(self):
3636 new_contents = self.NEWFILE_CONTENTS * 300000
3637 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3638 400, "Bad Request", "Unknown format: foo",
3639 self.PUT, self.public_url + \
3640 "/foo/foo.txt?format=foo",
3643 def test_PUT_NEWFILEURL_uri_replace(self):
3644 contents, n, new_uri = self.makefile(8)
3645 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3646 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3647 d.addCallback(lambda res:
3648 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3652 def test_PUT_NEWFILEURL_uri_no_replace(self):
3653 contents, n, new_uri = self.makefile(8)
3654 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3655 d.addBoth(self.shouldFail, error.Error,
3656 "PUT_NEWFILEURL_uri_no_replace",
3658 "There was already a child by that name, and you asked me "
3659 "to not replace it")
3662 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3663 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3664 d.addBoth(self.shouldFail, error.Error,
3665 "POST_put_uri_unknown_bad",
3667 "unknown cap in a write slot")
3670 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3671 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3672 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3673 u"put-future-ro.txt")
3676 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3677 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3678 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3679 u"put-future-imm.txt")
3682 def test_PUT_NEWFILE_URI(self):
3683 file_contents = "New file contents here\n"
3684 d = self.PUT("/uri", file_contents)
3686 assert isinstance(uri, str), uri
3687 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3688 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3690 return self.GET("/uri/%s" % uri)
3691 d.addCallback(_check)
3693 self.failUnlessReallyEqual(res, file_contents)
3694 d.addCallback(_check2)
3697 def test_PUT_NEWFILE_URI_not_mutable(self):
3698 file_contents = "New file contents here\n"
3699 d = self.PUT("/uri?mutable=false", file_contents)
3701 assert isinstance(uri, str), uri
3702 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3703 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3705 return self.GET("/uri/%s" % uri)
3706 d.addCallback(_check)
3708 self.failUnlessReallyEqual(res, file_contents)
3709 d.addCallback(_check2)
3712 def test_PUT_NEWFILE_URI_only_PUT(self):
3713 d = self.PUT("/uri?t=bogus", "")
3714 d.addBoth(self.shouldFail, error.Error,
3715 "PUT_NEWFILE_URI_only_PUT",
3717 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3720 def test_PUT_NEWFILE_URI_mutable(self):
3721 file_contents = "New file contents here\n"
3722 d = self.PUT("/uri?mutable=true", file_contents)
3723 def _check1(filecap):
3724 filecap = filecap.strip()
3725 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3726 self.filecap = filecap
3727 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3728 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
3729 n = self.s.create_node_from_uri(filecap)
3730 return n.download_best_version()
3731 d.addCallback(_check1)
3733 self.failUnlessReallyEqual(data, file_contents)
3734 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3735 d.addCallback(_check2)
3737 self.failUnlessReallyEqual(res, file_contents)
3738 d.addCallback(_check3)
3741 def test_PUT_mkdir(self):
3742 d = self.PUT("/uri?t=mkdir", "")
3744 n = self.s.create_node_from_uri(uri.strip())
3745 d2 = self.failUnlessNodeKeysAre(n, [])
3746 d2.addCallback(lambda res:
3747 self.GET("/uri/%s?t=json" % uri))
3749 d.addCallback(_check)
3750 d.addCallback(self.failUnlessIsEmptyJSON)
3753 def test_PUT_mkdir_mdmf(self):
3754 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3756 u = uri.from_string(res)
3757 # Check that this is an MDMF writecap
3758 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3762 def test_PUT_mkdir_sdmf(self):
3763 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3765 u = uri.from_string(res)
3766 self.failUnlessIsInstance(u, uri.DirectoryURI)
3770 def test_PUT_mkdir_bad_format(self):
3771 return self.shouldHTTPError("PUT_mkdir_bad_format",
3772 400, "Bad Request", "Unknown format: foo",
3773 self.PUT, "/uri?t=mkdir&format=foo",
3776 def test_POST_check(self):
3777 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3779 # this returns a string form of the results, which are probably
3780 # None since we're using fake filenodes.
3781 # TODO: verify that the check actually happened, by changing
3782 # FakeCHKFileNode to count how many times .check() has been
3785 d.addCallback(_done)
3789 def test_PUT_update_at_offset(self):
3790 file_contents = "test file" * 100000 # about 900 KiB
3791 d = self.PUT("/uri?mutable=true", file_contents)
3793 self.filecap = filecap
3794 new_data = file_contents[:100]
3795 new = "replaced and so on"
3797 new_data += file_contents[len(new_data):]
3798 assert len(new_data) == len(file_contents)
3799 self.new_data = new_data
3800 d.addCallback(_then)
3801 d.addCallback(lambda ignored:
3802 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3803 "replaced and so on"))
3804 def _get_data(filecap):
3805 n = self.s.create_node_from_uri(filecap)
3806 return n.download_best_version()
3807 d.addCallback(_get_data)
3808 d.addCallback(lambda results:
3809 self.failUnlessEqual(results, self.new_data))
3810 # Now try appending things to the file
3811 d.addCallback(lambda ignored:
3812 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3814 d.addCallback(_get_data)
3815 d.addCallback(lambda results:
3816 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3817 # and try replacing the beginning of the file
3818 d.addCallback(lambda ignored:
3819 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3820 d.addCallback(_get_data)
3821 d.addCallback(lambda results:
3822 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3825 def test_PUT_update_at_invalid_offset(self):
3826 file_contents = "test file" * 100000 # about 900 KiB
3827 d = self.PUT("/uri?mutable=true", file_contents)
3829 self.filecap = filecap
3830 d.addCallback(_then)
3831 # Negative offsets should cause an error.
3832 d.addCallback(lambda ignored:
3833 self.shouldHTTPError("PUT_update_at_invalid_offset",
3837 "/uri/%s?offset=-1" % self.filecap,
3841 def test_PUT_update_at_offset_immutable(self):
3842 file_contents = "Test file" * 100000
3843 d = self.PUT("/uri", file_contents)
3845 self.filecap = filecap
3846 d.addCallback(_then)
3847 d.addCallback(lambda ignored:
3848 self.shouldHTTPError("PUT_update_at_offset_immutable",
3852 "/uri/%s?offset=50" % self.filecap,
3857 def test_bad_method(self):
3858 url = self.webish_url + self.public_url + "/foo/bar.txt"
3859 d = self.shouldHTTPError("bad_method",
3860 501, "Not Implemented",
3861 "I don't know how to treat a BOGUS request.",
3862 client.getPage, url, method="BOGUS")
3865 def test_short_url(self):
3866 url = self.webish_url + "/uri"
3867 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3868 "I don't know how to treat a DELETE request.",
3869 client.getPage, url, method="DELETE")
3872 def test_ophandle_bad(self):
3873 url = self.webish_url + "/operations/bogus?t=status"
3874 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3875 "unknown/expired handle 'bogus'",
3876 client.getPage, url)
3879 def test_ophandle_cancel(self):
3880 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3881 followRedirect=True)
3882 d.addCallback(lambda ignored:
3883 self.GET("/operations/128?t=status&output=JSON"))
3885 data = simplejson.loads(res)
3886 self.failUnless("finished" in data, res)
3887 monitor = self.ws.root.child_operations.handles["128"][0]
3888 d = self.POST("/operations/128?t=cancel&output=JSON")
3890 data = simplejson.loads(res)
3891 self.failUnless("finished" in data, res)
3892 # t=cancel causes the handle to be forgotten
3893 self.failUnless(monitor.is_cancelled())
3894 d.addCallback(_check2)
3896 d.addCallback(_check1)
3897 d.addCallback(lambda ignored:
3898 self.shouldHTTPError("ophandle_cancel",
3899 404, "404 Not Found",
3900 "unknown/expired handle '128'",
3902 "/operations/128?t=status&output=JSON"))
3905 def test_ophandle_retainfor(self):
3906 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3907 followRedirect=True)
3908 d.addCallback(lambda ignored:
3909 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3911 data = simplejson.loads(res)
3912 self.failUnless("finished" in data, res)
3913 d.addCallback(_check1)
3914 # the retain-for=0 will cause the handle to be expired very soon
3915 d.addCallback(lambda ign:
3916 self.clock.advance(2.0))
3917 d.addCallback(lambda ignored:
3918 self.shouldHTTPError("ophandle_retainfor",
3919 404, "404 Not Found",
3920 "unknown/expired handle '129'",
3922 "/operations/129?t=status&output=JSON"))
3925 def test_ophandle_release_after_complete(self):
3926 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3927 followRedirect=True)
3928 d.addCallback(self.wait_for_operation, "130")
3929 d.addCallback(lambda ignored:
3930 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3931 # the release-after-complete=true will cause the handle to be expired
3932 d.addCallback(lambda ignored:
3933 self.shouldHTTPError("ophandle_release_after_complete",
3934 404, "404 Not Found",
3935 "unknown/expired handle '130'",
3937 "/operations/130?t=status&output=JSON"))
3940 def test_uncollected_ophandle_expiration(self):
3941 # uncollected ophandles should expire after 4 days
3942 def _make_uncollected_ophandle(ophandle):
3943 d = self.POST(self.public_url +
3944 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3945 followRedirect=False)
3946 # When we start the operation, the webapi server will want
3947 # to redirect us to the page for the ophandle, so we get
3948 # confirmation that the operation has started. If the
3949 # manifest operation has finished by the time we get there,
3950 # following that redirect (by setting followRedirect=True
3951 # above) has the side effect of collecting the ophandle that
3952 # we've just created, which means that we can't use the
3953 # ophandle to test the uncollected timeout anymore. So,
3954 # instead, catch the 302 here and don't follow it.
3955 d.addBoth(self.should302, "uncollected_ophandle_creation")
3957 # Create an ophandle, don't collect it, then advance the clock by
3958 # 4 days - 1 second and make sure that the ophandle is still there.
3959 d = _make_uncollected_ophandle(131)
3960 d.addCallback(lambda ign:
3961 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3962 d.addCallback(lambda ign:
3963 self.GET("/operations/131?t=status&output=JSON"))
3965 data = simplejson.loads(res)
3966 self.failUnless("finished" in data, res)
3967 d.addCallback(_check1)
3968 # Create an ophandle, don't collect it, then try to collect it
3969 # after 4 days. It should be gone.
3970 d.addCallback(lambda ign:
3971 _make_uncollected_ophandle(132))
3972 d.addCallback(lambda ign:
3973 self.clock.advance(96*60*60))
3974 d.addCallback(lambda ign:
3975 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3976 404, "404 Not Found",
3977 "unknown/expired handle '132'",
3979 "/operations/132?t=status&output=JSON"))
3982 def test_collected_ophandle_expiration(self):
3983 # collected ophandles should expire after 1 day
3984 def _make_collected_ophandle(ophandle):
3985 d = self.POST(self.public_url +
3986 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3987 followRedirect=True)
3988 # By following the initial redirect, we collect the ophandle
3989 # we've just created.
3991 # Create a collected ophandle, then collect it after 23 hours
3992 # and 59 seconds to make sure that it is still there.
3993 d = _make_collected_ophandle(133)
3994 d.addCallback(lambda ign:
3995 self.clock.advance((24*60*60) - 1))
3996 d.addCallback(lambda ign:
3997 self.GET("/operations/133?t=status&output=JSON"))
3999 data = simplejson.loads(res)
4000 self.failUnless("finished" in data, res)
4001 d.addCallback(_check1)
4002 # Create another uncollected ophandle, then try to collect it
4003 # after 24 hours to make sure that it is gone.
4004 d.addCallback(lambda ign:
4005 _make_collected_ophandle(134))
4006 d.addCallback(lambda ign:
4007 self.clock.advance(24*60*60))
4008 d.addCallback(lambda ign:
4009 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
4010 404, "404 Not Found",
4011 "unknown/expired handle '134'",
4013 "/operations/134?t=status&output=JSON"))
4016 def test_incident(self):
4017 d = self.POST("/report_incident", details="eek")
4019 self.failIfIn("<html>", res)
4020 self.failUnlessIn("Thank you for your report!", res)
4021 d.addCallback(_done)
4024 def test_static(self):
4025 webdir = os.path.join(self.staticdir, "subdir")
4026 fileutil.make_dirs(webdir)
4027 f = open(os.path.join(webdir, "hello.txt"), "wb")
4031 d = self.GET("/static/subdir/hello.txt")
4033 self.failUnlessReallyEqual(res, "hello")
4034 d.addCallback(_check)
4038 class IntroducerWeb(unittest.TestCase):
4043 d = defer.succeed(None)
4045 d.addCallback(lambda ign: self.node.stopService())
4046 d.addCallback(flushEventualQueue)
4049 def test_welcome(self):
4050 basedir = "web.IntroducerWeb.test_welcome"
4052 fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n")
4053 self.node = IntroducerNode(basedir)
4054 self.ws = self.node.getServiceNamed("webish")
4056 d = fireEventually(None)
4057 d.addCallback(lambda ign: self.node.startService())
4058 d.addCallback(lambda ign: self.node.when_tub_ready())
4060 d.addCallback(lambda ign: self.GET("/"))
4062 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
4063 self.failUnlessIn(FAVICON_MARKUP, res)
4064 d.addCallback(_check)
4067 def GET(self, urlpath, followRedirect=False, return_response=False,
4069 # if return_response=True, this fires with (data, statuscode,
4070 # respheaders) instead of just data.
4071 assert not isinstance(urlpath, unicode)
4072 url = self.ws.getURL().rstrip('/') + urlpath
4073 factory = HTTPClientGETFactory(url, method="GET",
4074 followRedirect=followRedirect, **kwargs)
4075 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
4076 d = factory.deferred
4077 def _got_data(data):
4078 return (data, factory.status, factory.response_headers)
4080 d.addCallback(_got_data)
4081 return factory.deferred
4084 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4085 def test_load_file(self):
4086 # This will raise an exception unless a well-formed XML file is found under that name.
4087 common.getxmlfile('directory.xhtml').load()
4089 def test_parse_replace_arg(self):
4090 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4091 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4092 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4094 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
4095 common.parse_replace_arg, "only_fles")
4097 def test_abbreviate_time(self):
4098 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4099 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4100 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4101 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4102 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4103 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4105 def test_compute_rate(self):
4106 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4107 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4108 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4109 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4110 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4111 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4112 self.shouldFail(AssertionError, "test_compute_rate", "",
4113 common.compute_rate, -100, 10)
4114 self.shouldFail(AssertionError, "test_compute_rate", "",
4115 common.compute_rate, 100, -10)
4118 rate = common.compute_rate(10*1000*1000, 1)
4119 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4121 def test_abbreviate_rate(self):
4122 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4123 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4124 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4125 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4127 def test_abbreviate_size(self):
4128 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4129 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4130 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4131 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4132 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4134 def test_plural(self):
4136 return "%d second%s" % (s, status.plural(s))
4137 self.failUnlessReallyEqual(convert(0), "0 seconds")
4138 self.failUnlessReallyEqual(convert(1), "1 second")
4139 self.failUnlessReallyEqual(convert(2), "2 seconds")
4141 return "has share%s: %s" % (status.plural(s), ",".join(s))
4142 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4143 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4144 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4147 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4149 def CHECK(self, ign, which, args, clientnum=0):
4150 fileurl = self.fileurls[which]
4151 url = fileurl + "?" + args
4152 return self.GET(url, method="POST", clientnum=clientnum)
4154 def test_filecheck(self):
4155 self.basedir = "web/Grid/filecheck"
4157 c0 = self.g.clients[0]
4160 d = c0.upload(upload.Data(DATA, convergence=""))
4161 def _stash_uri(ur, which):
4162 self.uris[which] = ur.uri
4163 d.addCallback(_stash_uri, "good")
4164 d.addCallback(lambda ign:
4165 c0.upload(upload.Data(DATA+"1", convergence="")))
4166 d.addCallback(_stash_uri, "sick")
4167 d.addCallback(lambda ign:
4168 c0.upload(upload.Data(DATA+"2", convergence="")))
4169 d.addCallback(_stash_uri, "dead")
4170 def _stash_mutable_uri(n, which):
4171 self.uris[which] = n.get_uri()
4172 assert isinstance(self.uris[which], str)
4173 d.addCallback(lambda ign:
4174 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4175 d.addCallback(_stash_mutable_uri, "corrupt")
4176 d.addCallback(lambda ign:
4177 c0.upload(upload.Data("literal", convergence="")))
4178 d.addCallback(_stash_uri, "small")
4179 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4180 d.addCallback(_stash_mutable_uri, "smalldir")
4182 def _compute_fileurls(ignored):
4184 for which in self.uris:
4185 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4186 d.addCallback(_compute_fileurls)
4188 def _clobber_shares(ignored):
4189 good_shares = self.find_uri_shares(self.uris["good"])
4190 self.failUnlessReallyEqual(len(good_shares), 10)
4191 sick_shares = self.find_uri_shares(self.uris["sick"])
4192 os.unlink(sick_shares[0][2])
4193 dead_shares = self.find_uri_shares(self.uris["dead"])
4194 for i in range(1, 10):
4195 os.unlink(dead_shares[i][2])
4196 c_shares = self.find_uri_shares(self.uris["corrupt"])
4197 cso = CorruptShareOptions()
4198 cso.stdout = StringIO()
4199 cso.parseOptions([c_shares[0][2]])
4201 d.addCallback(_clobber_shares)
4203 d.addCallback(self.CHECK, "good", "t=check")
4204 def _got_html_good(res):
4205 self.failUnlessIn("Healthy", res)
4206 self.failIfIn("Not Healthy", res)
4207 self.failUnlessIn(FAVICON_MARKUP, res)
4208 d.addCallback(_got_html_good)
4209 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4210 def _got_html_good_return_to(res):
4211 self.failUnlessIn("Healthy", res)
4212 self.failIfIn("Not Healthy", res)
4213 self.failUnlessIn('<a href="somewhere">Return to file', res)
4214 d.addCallback(_got_html_good_return_to)
4215 d.addCallback(self.CHECK, "good", "t=check&output=json")
4216 def _got_json_good(res):
4217 r = simplejson.loads(res)
4218 self.failUnlessEqual(r["summary"], "Healthy")
4219 self.failUnless(r["results"]["healthy"])
4220 self.failIf(r["results"]["needs-rebalancing"])
4221 self.failUnless(r["results"]["recoverable"])
4222 d.addCallback(_got_json_good)
4224 d.addCallback(self.CHECK, "small", "t=check")
4225 def _got_html_small(res):
4226 self.failUnlessIn("Literal files are always healthy", res)
4227 self.failIfIn("Not Healthy", res)
4228 d.addCallback(_got_html_small)
4229 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4230 def _got_html_small_return_to(res):
4231 self.failUnlessIn("Literal files are always healthy", res)
4232 self.failIfIn("Not Healthy", res)
4233 self.failUnlessIn('<a href="somewhere">Return to file', res)
4234 d.addCallback(_got_html_small_return_to)
4235 d.addCallback(self.CHECK, "small", "t=check&output=json")
4236 def _got_json_small(res):
4237 r = simplejson.loads(res)
4238 self.failUnlessEqual(r["storage-index"], "")
4239 self.failUnless(r["results"]["healthy"])
4240 d.addCallback(_got_json_small)
4242 d.addCallback(self.CHECK, "smalldir", "t=check")
4243 def _got_html_smalldir(res):
4244 self.failUnlessIn("Literal files are always healthy", res)
4245 self.failIfIn("Not Healthy", res)
4246 d.addCallback(_got_html_smalldir)
4247 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4248 def _got_json_smalldir(res):
4249 r = simplejson.loads(res)
4250 self.failUnlessEqual(r["storage-index"], "")
4251 self.failUnless(r["results"]["healthy"])
4252 d.addCallback(_got_json_smalldir)
4254 d.addCallback(self.CHECK, "sick", "t=check")
4255 def _got_html_sick(res):
4256 self.failUnlessIn("Not Healthy", res)
4257 d.addCallback(_got_html_sick)
4258 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4259 def _got_json_sick(res):
4260 r = simplejson.loads(res)
4261 self.failUnlessEqual(r["summary"],
4262 "Not Healthy: 9 shares (enc 3-of-10)")
4263 self.failIf(r["results"]["healthy"])
4264 self.failIf(r["results"]["needs-rebalancing"])
4265 self.failUnless(r["results"]["recoverable"])
4266 d.addCallback(_got_json_sick)
4268 d.addCallback(self.CHECK, "dead", "t=check")
4269 def _got_html_dead(res):
4270 self.failUnlessIn("Not Healthy", res)
4271 d.addCallback(_got_html_dead)
4272 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4273 def _got_json_dead(res):
4274 r = simplejson.loads(res)
4275 self.failUnlessEqual(r["summary"],
4276 "Not Healthy: 1 shares (enc 3-of-10)")
4277 self.failIf(r["results"]["healthy"])
4278 self.failIf(r["results"]["needs-rebalancing"])
4279 self.failIf(r["results"]["recoverable"])
4280 d.addCallback(_got_json_dead)
4282 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4283 def _got_html_corrupt(res):
4284 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4285 d.addCallback(_got_html_corrupt)
4286 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4287 def _got_json_corrupt(res):
4288 r = simplejson.loads(res)
4289 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4290 self.failIf(r["results"]["healthy"])
4291 self.failUnless(r["results"]["recoverable"])
4292 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4293 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4294 d.addCallback(_got_json_corrupt)
4296 d.addErrback(self.explain_web_error)
4299 def test_repair_html(self):
4300 self.basedir = "web/Grid/repair_html"
4302 c0 = self.g.clients[0]
4305 d = c0.upload(upload.Data(DATA, convergence=""))
4306 def _stash_uri(ur, which):
4307 self.uris[which] = ur.uri
4308 d.addCallback(_stash_uri, "good")
4309 d.addCallback(lambda ign:
4310 c0.upload(upload.Data(DATA+"1", convergence="")))
4311 d.addCallback(_stash_uri, "sick")
4312 d.addCallback(lambda ign:
4313 c0.upload(upload.Data(DATA+"2", convergence="")))
4314 d.addCallback(_stash_uri, "dead")
4315 def _stash_mutable_uri(n, which):
4316 self.uris[which] = n.get_uri()
4317 assert isinstance(self.uris[which], str)
4318 d.addCallback(lambda ign:
4319 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4320 d.addCallback(_stash_mutable_uri, "corrupt")
4322 def _compute_fileurls(ignored):
4324 for which in self.uris:
4325 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4326 d.addCallback(_compute_fileurls)
4328 def _clobber_shares(ignored):
4329 good_shares = self.find_uri_shares(self.uris["good"])
4330 self.failUnlessReallyEqual(len(good_shares), 10)
4331 sick_shares = self.find_uri_shares(self.uris["sick"])
4332 os.unlink(sick_shares[0][2])
4333 dead_shares = self.find_uri_shares(self.uris["dead"])
4334 for i in range(1, 10):
4335 os.unlink(dead_shares[i][2])
4336 c_shares = self.find_uri_shares(self.uris["corrupt"])
4337 cso = CorruptShareOptions()
4338 cso.stdout = StringIO()
4339 cso.parseOptions([c_shares[0][2]])
4341 d.addCallback(_clobber_shares)
4343 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4344 def _got_html_good(res):
4345 self.failUnlessIn("Healthy", res)
4346 self.failIfIn("Not Healthy", res)
4347 self.failUnlessIn("No repair necessary", res)
4348 self.failUnlessIn(FAVICON_MARKUP, res)
4349 d.addCallback(_got_html_good)
4351 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4352 def _got_html_sick(res):
4353 self.failUnlessIn("Healthy : healthy", res)
4354 self.failIfIn("Not Healthy", res)
4355 self.failUnlessIn("Repair successful", res)
4356 d.addCallback(_got_html_sick)
4358 # repair of a dead file will fail, of course, but it isn't yet
4359 # clear how this should be reported. Right now it shows up as
4362 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4363 #def _got_html_dead(res):
4365 # self.failUnlessIn("Healthy : healthy", res)
4366 # self.failIfIn("Not Healthy", res)
4367 # self.failUnlessIn("No repair necessary", res)
4368 #d.addCallback(_got_html_dead)
4370 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4371 def _got_html_corrupt(res):
4372 self.failUnlessIn("Healthy : Healthy", res)
4373 self.failIfIn("Not Healthy", res)
4374 self.failUnlessIn("Repair successful", res)
4375 d.addCallback(_got_html_corrupt)
4377 d.addErrback(self.explain_web_error)
4380 def test_repair_json(self):
4381 self.basedir = "web/Grid/repair_json"
4383 c0 = self.g.clients[0]
4386 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4387 def _stash_uri(ur, which):
4388 self.uris[which] = ur.uri
4389 d.addCallback(_stash_uri, "sick")
4391 def _compute_fileurls(ignored):
4393 for which in self.uris:
4394 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4395 d.addCallback(_compute_fileurls)
4397 def _clobber_shares(ignored):
4398 sick_shares = self.find_uri_shares(self.uris["sick"])
4399 os.unlink(sick_shares[0][2])
4400 d.addCallback(_clobber_shares)
4402 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4403 def _got_json_sick(res):
4404 r = simplejson.loads(res)
4405 self.failUnlessReallyEqual(r["repair-attempted"], True)
4406 self.failUnlessReallyEqual(r["repair-successful"], True)
4407 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4408 "Not Healthy: 9 shares (enc 3-of-10)")
4409 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4410 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4411 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4412 d.addCallback(_got_json_sick)
4414 d.addErrback(self.explain_web_error)
4417 def test_unknown(self, immutable=False):
4418 self.basedir = "web/Grid/unknown"
4420 self.basedir = "web/Grid/unknown-immutable"
4423 c0 = self.g.clients[0]
4427 # the future cap format may contain slashes, which must be tolerated
4428 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4432 name = u"future-imm"
4433 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4434 d = c0.create_immutable_dirnode({name: (future_node, {})})
4437 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4438 d = c0.create_dirnode()
4440 def _stash_root_and_create_file(n):
4442 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4443 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4445 return self.rootnode.set_node(name, future_node)
4446 d.addCallback(_stash_root_and_create_file)
4448 # make sure directory listing tolerates unknown nodes
4449 d.addCallback(lambda ign: self.GET(self.rooturl))
4450 def _check_directory_html(res, expected_type_suffix):
4451 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4452 '<td>%s</td>' % (expected_type_suffix, str(name)),
4454 self.failUnless(re.search(pattern, res), res)
4455 # find the More Info link for name, should be relative
4456 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4457 info_url = mo.group(1)
4458 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4460 d.addCallback(_check_directory_html, "-IMM")
4462 d.addCallback(_check_directory_html, "")
4464 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4465 def _check_directory_json(res, expect_rw_uri):
4466 data = simplejson.loads(res)
4467 self.failUnlessEqual(data[0], "dirnode")
4468 f = data[1]["children"][name]
4469 self.failUnlessEqual(f[0], "unknown")
4471 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4473 self.failIfIn("rw_uri", f[1])
4475 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4477 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4478 self.failUnlessIn("metadata", f[1])
4479 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4481 def _check_info(res, expect_rw_uri, expect_ro_uri):
4482 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4484 self.failUnlessIn(unknown_rwcap, res)
4487 self.failUnlessIn(unknown_immcap, res)
4489 self.failUnlessIn(unknown_rocap, res)
4491 self.failIfIn(unknown_rocap, res)
4492 self.failIfIn("Raw data as", res)
4493 self.failIfIn("Directory writecap", res)
4494 self.failIfIn("Checker Operations", res)
4495 self.failIfIn("Mutable File Operations", res)
4496 self.failIfIn("Directory Operations", res)
4498 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4499 # why they fail. Possibly related to ticket #922.
4501 d.addCallback(lambda ign: self.GET(expected_info_url))
4502 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4503 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4504 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4506 def _check_json(res, expect_rw_uri):
4507 data = simplejson.loads(res)
4508 self.failUnlessEqual(data[0], "unknown")
4510 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4512 self.failIfIn("rw_uri", data[1])
4515 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4516 self.failUnlessReallyEqual(data[1]["mutable"], False)
4518 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4519 self.failUnlessReallyEqual(data[1]["mutable"], True)
4521 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4522 self.failIfIn("mutable", data[1])
4524 # TODO: check metadata contents
4525 self.failUnlessIn("metadata", data[1])
4527 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4528 d.addCallback(_check_json, expect_rw_uri=not immutable)
4530 # and make sure that a read-only version of the directory can be
4531 # rendered too. This version will not have unknown_rwcap, whether
4532 # or not future_node was immutable.
4533 d.addCallback(lambda ign: self.GET(self.rourl))
4535 d.addCallback(_check_directory_html, "-IMM")
4537 d.addCallback(_check_directory_html, "-RO")
4539 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4540 d.addCallback(_check_directory_json, expect_rw_uri=False)
4542 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4543 d.addCallback(_check_json, expect_rw_uri=False)
4545 # TODO: check that getting t=info from the Info link in the ro directory
4546 # works, and does not include the writecap URI.
4549 def test_immutable_unknown(self):
4550 return self.test_unknown(immutable=True)
4552 def test_mutant_dirnodes_are_omitted(self):
4553 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4556 c = self.g.clients[0]
4561 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4562 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4563 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4565 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4566 # test the dirnode and web layers separately.
4568 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4569 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4570 # When the directory is read, the mutants should be silently disposed of, leaving
4571 # their lonely sibling.
4572 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4573 # because immutable directories don't have a writecap and therefore that field
4574 # isn't (and can't be) decrypted.
4575 # TODO: The field still exists in the netstring. Technically we should check what
4576 # happens if something is put there (_unpack_contents should raise ValueError),
4577 # but that can wait.
4579 lonely_child = nm.create_from_cap(lonely_uri)
4580 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4581 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4583 def _by_hook_or_by_crook():
4585 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4586 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4588 mutant_write_in_ro_child.get_write_uri = lambda: None
4589 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4591 kids = {u"lonely": (lonely_child, {}),
4592 u"ro": (mutant_ro_child, {}),
4593 u"write-in-ro": (mutant_write_in_ro_child, {}),
4595 d = c.create_immutable_dirnode(kids)
4598 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4599 self.failIf(dn.is_mutable())
4600 self.failUnless(dn.is_readonly())
4601 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4602 self.failIf(hasattr(dn._node, 'get_writekey'))
4604 self.failUnlessIn("RO-IMM", rep)
4606 self.failUnlessIn("CHK", cap.to_string())
4609 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4610 return download_to_data(dn._node)
4611 d.addCallback(_created)
4613 def _check_data(data):
4614 # Decode the netstring representation of the directory to check that all children
4615 # are present. This is a bit of an abstraction violation, but there's not really
4616 # any other way to do it given that the real DirectoryNode._unpack_contents would
4617 # strip the mutant children out (which is what we're trying to test, later).
4620 while position < len(data):
4621 entries, position = split_netstring(data, 1, position)
4623 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4624 name = name_utf8.decode("utf-8")
4625 self.failUnlessEqual(rwcapdata, "")
4626 self.failUnlessIn(name, kids)
4627 (expected_child, ign) = kids[name]
4628 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4631 self.failUnlessReallyEqual(numkids, 3)
4632 return self.rootnode.list()
4633 d.addCallback(_check_data)
4635 # Now when we use the real directory listing code, the mutants should be absent.
4636 def _check_kids(children):
4637 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4638 lonely_node, lonely_metadata = children[u"lonely"]
4640 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4641 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4642 d.addCallback(_check_kids)
4644 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4645 d.addCallback(lambda n: n.list())
4646 d.addCallback(_check_kids) # again with dirnode recreated from cap
4648 # Make sure the lonely child can be listed in HTML...
4649 d.addCallback(lambda ign: self.GET(self.rooturl))
4650 def _check_html(res):
4651 self.failIfIn("URI:SSK", res)
4652 get_lonely = "".join([r'<td>FILE</td>',
4654 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4656 r'\s+<td align="right">%d</td>' % len("one"),
4658 self.failUnless(re.search(get_lonely, res), res)
4660 # find the More Info link for name, should be relative
4661 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4662 info_url = mo.group(1)
4663 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4664 d.addCallback(_check_html)
4667 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4668 def _check_json(res):
4669 data = simplejson.loads(res)
4670 self.failUnlessEqual(data[0], "dirnode")
4671 listed_children = data[1]["children"]
4672 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4673 ll_type, ll_data = listed_children[u"lonely"]
4674 self.failUnlessEqual(ll_type, "filenode")
4675 self.failIfIn("rw_uri", ll_data)
4676 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4677 d.addCallback(_check_json)
4680 def test_deep_check(self):
4681 self.basedir = "web/Grid/deep_check"
4683 c0 = self.g.clients[0]
4687 d = c0.create_dirnode()
4688 def _stash_root_and_create_file(n):
4690 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4691 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4692 d.addCallback(_stash_root_and_create_file)
4693 def _stash_uri(fn, which):
4694 self.uris[which] = fn.get_uri()
4696 d.addCallback(_stash_uri, "good")
4697 d.addCallback(lambda ign:
4698 self.rootnode.add_file(u"small",
4699 upload.Data("literal",
4701 d.addCallback(_stash_uri, "small")
4702 d.addCallback(lambda ign:
4703 self.rootnode.add_file(u"sick",
4704 upload.Data(DATA+"1",
4706 d.addCallback(_stash_uri, "sick")
4708 # this tests that deep-check and stream-manifest will ignore
4709 # UnknownNode instances. Hopefully this will also cover deep-stats.
4710 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4711 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4713 def _clobber_shares(ignored):
4714 self.delete_shares_numbered(self.uris["sick"], [0,1])
4715 d.addCallback(_clobber_shares)
4723 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4726 units = [simplejson.loads(line)
4727 for line in res.splitlines()
4730 print "response is:", res
4731 print "undecodeable line was '%s'" % line
4733 self.failUnlessReallyEqual(len(units), 5+1)
4734 # should be parent-first
4736 self.failUnlessEqual(u0["path"], [])
4737 self.failUnlessEqual(u0["type"], "directory")
4738 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4739 u0cr = u0["check-results"]
4740 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4742 ugood = [u for u in units
4743 if u["type"] == "file" and u["path"] == [u"good"]][0]
4744 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4745 ugoodcr = ugood["check-results"]
4746 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4749 self.failUnlessEqual(stats["type"], "stats")
4751 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4752 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4753 self.failUnlessReallyEqual(s["count-directories"], 1)
4754 self.failUnlessReallyEqual(s["count-unknown"], 1)
4755 d.addCallback(_done)
4757 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4758 def _check_manifest(res):
4759 self.failUnless(res.endswith("\n"))
4760 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4761 self.failUnlessReallyEqual(len(units), 5+1)
4762 self.failUnlessEqual(units[-1]["type"], "stats")
4764 self.failUnlessEqual(first["path"], [])
4765 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4766 self.failUnlessEqual(first["type"], "directory")
4767 stats = units[-1]["stats"]
4768 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4769 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4770 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4771 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4772 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4773 d.addCallback(_check_manifest)
4775 # now add root/subdir and root/subdir/grandchild, then make subdir
4776 # unrecoverable, then see what happens
4778 d.addCallback(lambda ign:
4779 self.rootnode.create_subdirectory(u"subdir"))
4780 d.addCallback(_stash_uri, "subdir")
4781 d.addCallback(lambda subdir_node:
4782 subdir_node.add_file(u"grandchild",
4783 upload.Data(DATA+"2",
4785 d.addCallback(_stash_uri, "grandchild")
4787 d.addCallback(lambda ign:
4788 self.delete_shares_numbered(self.uris["subdir"],
4796 # root/subdir [unrecoverable]
4797 # root/subdir/grandchild
4799 # how should a streaming-JSON API indicate fatal error?
4800 # answer: emit ERROR: instead of a JSON string
4802 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4803 def _check_broken_manifest(res):
4804 lines = res.splitlines()
4806 for (i,line) in enumerate(lines)
4807 if line.startswith("ERROR:")]
4809 self.fail("no ERROR: in output: %s" % (res,))
4810 first_error = error_lines[0]
4811 error_line = lines[first_error]
4812 error_msg = lines[first_error+1:]
4813 error_msg_s = "\n".join(error_msg) + "\n"
4814 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4816 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4817 units = [simplejson.loads(line) for line in lines[:first_error]]
4818 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4819 last_unit = units[-1]
4820 self.failUnlessEqual(last_unit["path"], ["subdir"])
4821 d.addCallback(_check_broken_manifest)
4823 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4824 def _check_broken_deepcheck(res):
4825 lines = res.splitlines()
4827 for (i,line) in enumerate(lines)
4828 if line.startswith("ERROR:")]
4830 self.fail("no ERROR: in output: %s" % (res,))
4831 first_error = error_lines[0]
4832 error_line = lines[first_error]
4833 error_msg = lines[first_error+1:]
4834 error_msg_s = "\n".join(error_msg) + "\n"
4835 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4837 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4838 units = [simplejson.loads(line) for line in lines[:first_error]]
4839 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4840 last_unit = units[-1]
4841 self.failUnlessEqual(last_unit["path"], ["subdir"])
4842 r = last_unit["check-results"]["results"]
4843 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4844 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4845 self.failUnlessReallyEqual(r["recoverable"], False)
4846 d.addCallback(_check_broken_deepcheck)
4848 d.addErrback(self.explain_web_error)
4851 def test_deep_check_and_repair(self):
4852 self.basedir = "web/Grid/deep_check_and_repair"
4854 c0 = self.g.clients[0]
4858 d = c0.create_dirnode()
4859 def _stash_root_and_create_file(n):
4861 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4862 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4863 d.addCallback(_stash_root_and_create_file)
4864 def _stash_uri(fn, which):
4865 self.uris[which] = fn.get_uri()
4866 d.addCallback(_stash_uri, "good")
4867 d.addCallback(lambda ign:
4868 self.rootnode.add_file(u"small",
4869 upload.Data("literal",
4871 d.addCallback(_stash_uri, "small")
4872 d.addCallback(lambda ign:
4873 self.rootnode.add_file(u"sick",
4874 upload.Data(DATA+"1",
4876 d.addCallback(_stash_uri, "sick")
4877 #d.addCallback(lambda ign:
4878 # self.rootnode.add_file(u"dead",
4879 # upload.Data(DATA+"2",
4881 #d.addCallback(_stash_uri, "dead")
4883 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4884 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4885 #d.addCallback(_stash_uri, "corrupt")
4887 def _clobber_shares(ignored):
4888 good_shares = self.find_uri_shares(self.uris["good"])
4889 self.failUnlessReallyEqual(len(good_shares), 10)
4890 sick_shares = self.find_uri_shares(self.uris["sick"])
4891 os.unlink(sick_shares[0][2])
4892 #dead_shares = self.find_uri_shares(self.uris["dead"])
4893 #for i in range(1, 10):
4894 # os.unlink(dead_shares[i][2])
4896 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4897 #cso = CorruptShareOptions()
4898 #cso.stdout = StringIO()
4899 #cso.parseOptions([c_shares[0][2]])
4901 d.addCallback(_clobber_shares)
4904 # root/good CHK, 10 shares
4906 # root/sick CHK, 9 shares
4908 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4910 units = [simplejson.loads(line)
4911 for line in res.splitlines()
4913 self.failUnlessReallyEqual(len(units), 4+1)
4914 # should be parent-first
4916 self.failUnlessEqual(u0["path"], [])
4917 self.failUnlessEqual(u0["type"], "directory")
4918 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4919 u0crr = u0["check-and-repair-results"]
4920 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4921 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4923 ugood = [u for u in units
4924 if u["type"] == "file" and u["path"] == [u"good"]][0]
4925 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4926 ugoodcrr = ugood["check-and-repair-results"]
4927 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4928 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4930 usick = [u for u in units
4931 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4932 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4933 usickcrr = usick["check-and-repair-results"]
4934 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4935 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4936 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4937 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4940 self.failUnlessEqual(stats["type"], "stats")
4942 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4943 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4944 self.failUnlessReallyEqual(s["count-directories"], 1)
4945 d.addCallback(_done)
4947 d.addErrback(self.explain_web_error)
4950 def _count_leases(self, ignored, which):
4951 u = self.uris[which]
4952 shares = self.find_uri_shares(u)
4954 for shnum, serverid, fn in shares:
4955 sf = get_share_file(fn)
4956 num_leases = len(list(sf.get_leases()))
4957 lease_counts.append( (fn, num_leases) )
4960 def _assert_leasecount(self, lease_counts, expected):
4961 for (fn, num_leases) in lease_counts:
4962 if num_leases != expected:
4963 self.fail("expected %d leases, have %d, on %s" %
4964 (expected, num_leases, fn))
4966 def test_add_lease(self):
4967 self.basedir = "web/Grid/add_lease"
4968 self.set_up_grid(num_clients=2)
4969 c0 = self.g.clients[0]
4972 d = c0.upload(upload.Data(DATA, convergence=""))
4973 def _stash_uri(ur, which):
4974 self.uris[which] = ur.uri
4975 d.addCallback(_stash_uri, "one")
4976 d.addCallback(lambda ign:
4977 c0.upload(upload.Data(DATA+"1", convergence="")))
4978 d.addCallback(_stash_uri, "two")
4979 def _stash_mutable_uri(n, which):
4980 self.uris[which] = n.get_uri()
4981 assert isinstance(self.uris[which], str)
4982 d.addCallback(lambda ign:
4983 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4984 d.addCallback(_stash_mutable_uri, "mutable")
4986 def _compute_fileurls(ignored):
4988 for which in self.uris:
4989 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4990 d.addCallback(_compute_fileurls)
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 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
5000 def _got_html_good(res):
5001 self.failUnlessIn("Healthy", res)
5002 self.failIfIn("Not Healthy", res)
5003 d.addCallback(_got_html_good)
5005 d.addCallback(self._count_leases, "one")
5006 d.addCallback(self._assert_leasecount, 1)
5007 d.addCallback(self._count_leases, "two")
5008 d.addCallback(self._assert_leasecount, 1)
5009 d.addCallback(self._count_leases, "mutable")
5010 d.addCallback(self._assert_leasecount, 1)
5012 # this CHECK uses the original client, which uses the same
5013 # lease-secrets, so it will just renew the original lease
5014 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
5015 d.addCallback(_got_html_good)
5017 d.addCallback(self._count_leases, "one")
5018 d.addCallback(self._assert_leasecount, 1)
5019 d.addCallback(self._count_leases, "two")
5020 d.addCallback(self._assert_leasecount, 1)
5021 d.addCallback(self._count_leases, "mutable")
5022 d.addCallback(self._assert_leasecount, 1)
5024 # this CHECK uses an alternate client, which adds a second lease
5025 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
5026 d.addCallback(_got_html_good)
5028 d.addCallback(self._count_leases, "one")
5029 d.addCallback(self._assert_leasecount, 2)
5030 d.addCallback(self._count_leases, "two")
5031 d.addCallback(self._assert_leasecount, 1)
5032 d.addCallback(self._count_leases, "mutable")
5033 d.addCallback(self._assert_leasecount, 1)
5035 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
5036 d.addCallback(_got_html_good)
5038 d.addCallback(self._count_leases, "one")
5039 d.addCallback(self._assert_leasecount, 2)
5040 d.addCallback(self._count_leases, "two")
5041 d.addCallback(self._assert_leasecount, 1)
5042 d.addCallback(self._count_leases, "mutable")
5043 d.addCallback(self._assert_leasecount, 1)
5045 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
5047 d.addCallback(_got_html_good)
5049 d.addCallback(self._count_leases, "one")
5050 d.addCallback(self._assert_leasecount, 2)
5051 d.addCallback(self._count_leases, "two")
5052 d.addCallback(self._assert_leasecount, 1)
5053 d.addCallback(self._count_leases, "mutable")
5054 d.addCallback(self._assert_leasecount, 2)
5056 d.addErrback(self.explain_web_error)
5059 def test_deep_add_lease(self):
5060 self.basedir = "web/Grid/deep_add_lease"
5061 self.set_up_grid(num_clients=2)
5062 c0 = self.g.clients[0]
5066 d = c0.create_dirnode()
5067 def _stash_root_and_create_file(n):
5069 self.uris["root"] = n.get_uri()
5070 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5071 return n.add_file(u"one", upload.Data(DATA, convergence=""))
5072 d.addCallback(_stash_root_and_create_file)
5073 def _stash_uri(fn, which):
5074 self.uris[which] = fn.get_uri()
5075 d.addCallback(_stash_uri, "one")
5076 d.addCallback(lambda ign:
5077 self.rootnode.add_file(u"small",
5078 upload.Data("literal",
5080 d.addCallback(_stash_uri, "small")
5082 d.addCallback(lambda ign:
5083 c0.create_mutable_file(publish.MutableData("mutable")))
5084 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5085 d.addCallback(_stash_uri, "mutable")
5087 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5089 units = [simplejson.loads(line)
5090 for line in res.splitlines()
5092 # root, one, small, mutable, stats
5093 self.failUnlessReallyEqual(len(units), 4+1)
5094 d.addCallback(_done)
5096 d.addCallback(self._count_leases, "root")
5097 d.addCallback(self._assert_leasecount, 1)
5098 d.addCallback(self._count_leases, "one")
5099 d.addCallback(self._assert_leasecount, 1)
5100 d.addCallback(self._count_leases, "mutable")
5101 d.addCallback(self._assert_leasecount, 1)
5103 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5104 d.addCallback(_done)
5106 d.addCallback(self._count_leases, "root")
5107 d.addCallback(self._assert_leasecount, 1)
5108 d.addCallback(self._count_leases, "one")
5109 d.addCallback(self._assert_leasecount, 1)
5110 d.addCallback(self._count_leases, "mutable")
5111 d.addCallback(self._assert_leasecount, 1)
5113 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5115 d.addCallback(_done)
5117 d.addCallback(self._count_leases, "root")
5118 d.addCallback(self._assert_leasecount, 2)
5119 d.addCallback(self._count_leases, "one")
5120 d.addCallback(self._assert_leasecount, 2)
5121 d.addCallback(self._count_leases, "mutable")
5122 d.addCallback(self._assert_leasecount, 2)
5124 d.addErrback(self.explain_web_error)
5128 def test_exceptions(self):
5129 self.basedir = "web/Grid/exceptions"
5130 self.set_up_grid(num_clients=1, num_servers=2)
5131 c0 = self.g.clients[0]
5132 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
5135 d = c0.create_dirnode()
5137 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5138 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5140 d.addCallback(_stash_root)
5141 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5143 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
5144 self.delete_shares_numbered(ur.uri, range(1,10))
5146 u = uri.from_string(ur.uri)
5147 u.key = testutil.flip_bit(u.key, 0)
5148 baduri = u.to_string()
5149 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5150 d.addCallback(_stash_bad)
5151 d.addCallback(lambda ign: c0.create_dirnode())
5152 def _mangle_dirnode_1share(n):
5154 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5155 self.fileurls["dir-1share-json"] = url + "?t=json"
5156 self.delete_shares_numbered(u, range(1,10))
5157 d.addCallback(_mangle_dirnode_1share)
5158 d.addCallback(lambda ign: c0.create_dirnode())
5159 def _mangle_dirnode_0share(n):
5161 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5162 self.fileurls["dir-0share-json"] = url + "?t=json"
5163 self.delete_shares_numbered(u, range(0,10))
5164 d.addCallback(_mangle_dirnode_0share)
5166 # NotEnoughSharesError should be reported sensibly, with a
5167 # text/plain explanation of the problem, and perhaps some
5168 # information on which shares *could* be found.
5170 d.addCallback(lambda ignored:
5171 self.shouldHTTPError("GET unrecoverable",
5172 410, "Gone", "NoSharesError",
5173 self.GET, self.fileurls["0shares"]))
5174 def _check_zero_shares(body):
5175 self.failIfIn("<html>", body)
5176 body = " ".join(body.strip().split())
5177 exp = ("NoSharesError: no shares could be found. "
5178 "Zero shares usually indicates a corrupt URI, or that "
5179 "no servers were connected, but it might also indicate "
5180 "severe corruption. You should perform a filecheck on "
5181 "this object to learn more. The full error message is: "
5182 "no shares (need 3). Last failure: None")
5183 self.failUnlessReallyEqual(exp, body)
5184 d.addCallback(_check_zero_shares)
5187 d.addCallback(lambda ignored:
5188 self.shouldHTTPError("GET 1share",
5189 410, "Gone", "NotEnoughSharesError",
5190 self.GET, self.fileurls["1share"]))
5191 def _check_one_share(body):
5192 self.failIfIn("<html>", body)
5193 body = " ".join(body.strip().split())
5194 msgbase = ("NotEnoughSharesError: This indicates that some "
5195 "servers were unavailable, or that shares have been "
5196 "lost to server departure, hard drive failure, or disk "
5197 "corruption. You should perform a filecheck on "
5198 "this object to learn more. The full error message is:"
5200 msg1 = msgbase + (" ran out of shares:"
5203 " overdue= unused= need 3. Last failure: None")
5204 msg2 = msgbase + (" ran out of shares:"
5206 " pending=Share(sh0-on-xgru5)"
5207 " overdue= unused= need 3. Last failure: None")
5208 self.failUnless(body == msg1 or body == msg2, body)
5209 d.addCallback(_check_one_share)
5211 d.addCallback(lambda ignored:
5212 self.shouldHTTPError("GET imaginary",
5213 404, "Not Found", None,
5214 self.GET, self.fileurls["imaginary"]))
5215 def _missing_child(body):
5216 self.failUnlessIn("No such child: imaginary", body)
5217 d.addCallback(_missing_child)
5219 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5220 def _check_0shares_dir_html(body):
5221 self.failUnlessIn("<html>", body)
5222 # we should see the regular page, but without the child table or
5224 body = " ".join(body.strip().split())
5225 self.failUnlessIn('href="?t=info">More info on this directory',
5227 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5228 "could not be retrieved, because there were insufficient "
5229 "good shares. This might indicate that no servers were "
5230 "connected, insufficient servers were connected, the URI "
5231 "was corrupt, or that shares have been lost due to server "
5232 "departure, hard drive failure, or disk corruption. You "
5233 "should perform a filecheck on this object to learn more.")
5234 self.failUnlessIn(exp, body)
5235 self.failUnlessIn("No upload forms: directory is unreadable", body)
5236 d.addCallback(_check_0shares_dir_html)
5238 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5239 def _check_1shares_dir_html(body):
5240 # at some point, we'll split UnrecoverableFileError into 0-shares
5241 # and some-shares like we did for immutable files (since there
5242 # are different sorts of advice to offer in each case). For now,
5243 # they present the same way.
5244 self.failUnlessIn("<html>", body)
5245 body = " ".join(body.strip().split())
5246 self.failUnlessIn('href="?t=info">More info on this directory',
5248 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5249 "could not be retrieved, because there were insufficient "
5250 "good shares. This might indicate that no servers were "
5251 "connected, insufficient servers were connected, the URI "
5252 "was corrupt, or that shares have been lost due to server "
5253 "departure, hard drive failure, or disk corruption. You "
5254 "should perform a filecheck on this object to learn more.")
5255 self.failUnlessIn(exp, body)
5256 self.failUnlessIn("No upload forms: directory is unreadable", body)
5257 d.addCallback(_check_1shares_dir_html)
5259 d.addCallback(lambda ignored:
5260 self.shouldHTTPError("GET dir-0share-json",
5261 410, "Gone", "UnrecoverableFileError",
5263 self.fileurls["dir-0share-json"]))
5264 def _check_unrecoverable_file(body):
5265 self.failIfIn("<html>", body)
5266 body = " ".join(body.strip().split())
5267 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5268 "could not be retrieved, because there were insufficient "
5269 "good shares. This might indicate that no servers were "
5270 "connected, insufficient servers were connected, the URI "
5271 "was corrupt, or that shares have been lost due to server "
5272 "departure, hard drive failure, or disk corruption. You "
5273 "should perform a filecheck on this object to learn more.")
5274 self.failUnlessReallyEqual(exp, body)
5275 d.addCallback(_check_unrecoverable_file)
5277 d.addCallback(lambda ignored:
5278 self.shouldHTTPError("GET dir-1share-json",
5279 410, "Gone", "UnrecoverableFileError",
5281 self.fileurls["dir-1share-json"]))
5282 d.addCallback(_check_unrecoverable_file)
5284 d.addCallback(lambda ignored:
5285 self.shouldHTTPError("GET imaginary",
5286 404, "Not Found", None,
5287 self.GET, self.fileurls["imaginary"]))
5289 # attach a webapi child that throws a random error, to test how it
5291 w = c0.getServiceNamed("webish")
5292 w.root.putChild("ERRORBOOM", ErrorBoom())
5294 # "Accept: */*" : should get a text/html stack trace
5295 # "Accept: text/plain" : should get a text/plain stack trace
5296 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5297 # no Accept header: should get a text/html stack trace
5299 d.addCallback(lambda ignored:
5300 self.shouldHTTPError("GET errorboom_html",
5301 500, "Internal Server Error", None,
5302 self.GET, "ERRORBOOM",
5303 headers={"accept": ["*/*"]}))
5304 def _internal_error_html1(body):
5305 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5306 d.addCallback(_internal_error_html1)
5308 d.addCallback(lambda ignored:
5309 self.shouldHTTPError("GET errorboom_text",
5310 500, "Internal Server Error", None,
5311 self.GET, "ERRORBOOM",
5312 headers={"accept": ["text/plain"]}))
5313 def _internal_error_text2(body):
5314 self.failIfIn("<html>", body)
5315 self.failUnless(body.startswith("Traceback "), body)
5316 d.addCallback(_internal_error_text2)
5318 CLI_accepts = "text/plain, application/octet-stream"
5319 d.addCallback(lambda ignored:
5320 self.shouldHTTPError("GET errorboom_text",
5321 500, "Internal Server Error", None,
5322 self.GET, "ERRORBOOM",
5323 headers={"accept": [CLI_accepts]}))
5324 def _internal_error_text3(body):
5325 self.failIfIn("<html>", body)
5326 self.failUnless(body.startswith("Traceback "), body)
5327 d.addCallback(_internal_error_text3)
5329 d.addCallback(lambda ignored:
5330 self.shouldHTTPError("GET errorboom_text",
5331 500, "Internal Server Error", None,
5332 self.GET, "ERRORBOOM"))
5333 def _internal_error_html4(body):
5334 self.failUnlessIn("<html>", body)
5335 d.addCallback(_internal_error_html4)
5337 def _flush_errors(res):
5338 # Trial: please ignore the CompletelyUnhandledError in the logs
5339 self.flushLoggedErrors(CompletelyUnhandledError)
5341 d.addBoth(_flush_errors)
5345 def test_blacklist(self):
5346 # download from a blacklisted URI, get an error
5347 self.basedir = "web/Grid/blacklist"
5349 c0 = self.g.clients[0]
5350 c0_basedir = c0.basedir
5351 fn = os.path.join(c0_basedir, "access.blacklist")
5353 DATA = "off-limits " * 50
5355 d = c0.upload(upload.Data(DATA, convergence=""))
5356 def _stash_uri_and_create_dir(ur):
5358 self.url = "uri/"+self.uri
5359 u = uri.from_string_filenode(self.uri)
5360 self.si = u.get_storage_index()
5361 childnode = c0.create_node_from_uri(self.uri, None)
5362 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5363 d.addCallback(_stash_uri_and_create_dir)
5364 def _stash_dir(node):
5365 self.dir_node = node
5366 self.dir_uri = node.get_uri()
5367 self.dir_url = "uri/"+self.dir_uri
5368 d.addCallback(_stash_dir)
5369 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5370 def _check_dir_html(body):
5371 self.failUnlessIn("<html>", body)
5372 self.failUnlessIn("blacklisted.txt</a>", body)
5373 d.addCallback(_check_dir_html)
5374 d.addCallback(lambda ign: self.GET(self.url))
5375 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5377 def _blacklist(ign):
5379 f.write(" # this is a comment\n")
5381 f.write("\n") # also exercise blank lines
5382 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5384 # clients should be checking the blacklist each time, so we don't
5385 # need to restart the client
5386 d.addCallback(_blacklist)
5387 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5389 "Access Prohibited: off-limits",
5390 self.GET, self.url))
5392 # We should still be able to list the parent directory, in HTML...
5393 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5394 def _check_dir_html2(body):
5395 self.failUnlessIn("<html>", body)
5396 self.failUnlessIn("blacklisted.txt</strike>", body)
5397 d.addCallback(_check_dir_html2)
5399 # ... and in JSON (used by CLI).
5400 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5401 def _check_dir_json(res):
5402 data = simplejson.loads(res)
5403 self.failUnless(isinstance(data, list), data)
5404 self.failUnlessEqual(data[0], "dirnode")
5405 self.failUnless(isinstance(data[1], dict), data)
5406 self.failUnlessIn("children", data[1])
5407 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5408 childdata = data[1]["children"]["blacklisted.txt"]
5409 self.failUnless(isinstance(childdata, list), data)
5410 self.failUnlessEqual(childdata[0], "filenode")
5411 self.failUnless(isinstance(childdata[1], dict), data)
5412 d.addCallback(_check_dir_json)
5414 def _unblacklist(ign):
5415 open(fn, "w").close()
5416 # the Blacklist object watches mtime to tell when the file has
5417 # changed, but on windows this test will run faster than the
5418 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5419 # to force a reload.
5420 self.g.clients[0].blacklist.last_mtime -= 2.0
5421 d.addCallback(_unblacklist)
5423 # now a read should work
5424 d.addCallback(lambda ign: self.GET(self.url))
5425 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5427 # read again to exercise the blacklist-is-unchanged logic
5428 d.addCallback(lambda ign: self.GET(self.url))
5429 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5431 # now add a blacklisted directory, and make sure files under it are
5434 childnode = c0.create_node_from_uri(self.uri, None)
5435 return c0.create_dirnode({u"child": (childnode,{}) })
5436 d.addCallback(_add_dir)
5437 def _get_dircap(dn):
5438 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5439 self.dir_url_base = "uri/"+dn.get_write_uri()
5440 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5441 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5442 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5443 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5444 d.addCallback(_get_dircap)
5445 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5446 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5447 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5448 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5449 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5450 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5451 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5452 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5453 d.addCallback(lambda ign: self.GET(self.child_url))
5454 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5456 def _block_dir(ign):
5458 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5460 self.g.clients[0].blacklist.last_mtime -= 2.0
5461 d.addCallback(_block_dir)
5462 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5464 "Access Prohibited: dir-off-limits",
5465 self.GET, self.dir_url_base))
5466 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5468 "Access Prohibited: dir-off-limits",
5469 self.GET, self.dir_url_json1))
5470 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5472 "Access Prohibited: dir-off-limits",
5473 self.GET, self.dir_url_json2))
5474 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5476 "Access Prohibited: dir-off-limits",
5477 self.GET, self.dir_url_json_ro))
5478 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5480 "Access Prohibited: dir-off-limits",
5481 self.GET, self.child_url))
5485 class CompletelyUnhandledError(Exception):
5487 class ErrorBoom(rend.Page):
5488 def beforeRender(self, ctx):
5489 raise CompletelyUnhandledError("whoops")