1 import os.path, re, urllib, time, cgi
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.util import escapeToXML
15 from nevow import rend
17 from allmydata import interfaces, uri, webish, dirnode
18 from allmydata.storage.shares import get_share_file
19 from allmydata.storage_client import StorageFarmBroker, StubServer
20 from allmydata.immutable import upload
21 from allmydata.immutable.downloader.status import DownloadStatus
22 from allmydata.dirnode import DirectoryNode
23 from allmydata.nodemaker import NodeMaker
24 from allmydata.unknown import UnknownNode
25 from allmydata.web import status, common
26 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
27 from allmydata.util import fileutil, base32, hashutil
28 from allmydata.util.consumer import download_to_data
29 from allmydata.util.netstring import split_netstring
30 from allmydata.util.encodingutil import to_str
31 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
32 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
33 make_mutable_file_uri, create_mutable_filenode
34 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
35 from allmydata.mutable import servermap, publish, retrieve
36 import allmydata.test.common_util as testutil
37 from allmydata.test.no_network import GridTestMixin
38 from allmydata.test.common_web import HTTPClientGETFactory, \
40 from allmydata.client import Client, SecretHolder
41 from allmydata.introducer import IntroducerNode
43 # create a fake uploader/downloader, and a couple of fake dirnodes, then
44 # create a webserver that works against them
46 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
48 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
49 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
50 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
52 FAVICON_MARKUP = '<link href="/icon.png" rel="shortcut icon" />'
53 DIR_HTML_TAG = '<html lang="en">'
55 class FakeStatsProvider:
57 stats = {'stats': {}, 'counters': {}}
60 class FakeNodeMaker(NodeMaker):
65 'max_segment_size':128*1024 # 1024=KiB
67 def _create_lit(self, cap):
68 return FakeCHKFileNode(cap, self.all_contents)
69 def _create_immutable(self, cap):
70 return FakeCHKFileNode(cap, self.all_contents)
71 def _create_mutable(self, cap):
72 return FakeMutableFileNode(None, None,
73 self.encoding_params, None,
74 self.all_contents).init_from_cap(cap)
75 def create_mutable_file(self, contents="", keysize=None,
76 version=SDMF_VERSION):
77 n = FakeMutableFileNode(None, None, self.encoding_params, None,
79 return n.create(contents, version=version)
81 class FakeUploader(service.Service):
84 helper_connected = False
86 def upload(self, uploadable):
87 d = uploadable.get_size()
88 d.addCallback(lambda size: uploadable.read(size))
91 n = create_chk_filenode(data, self.all_contents)
92 ur = upload.UploadResults(file_size=len(data),
99 uri_extension_data={},
100 uri_extension_hash="fake",
101 verifycapstr="fakevcap")
102 ur.set_uri(n.get_uri())
104 d.addCallback(_got_data)
107 def get_helper_info(self):
108 return (self.helper_furl, self.helper_connected)
112 ds = DownloadStatus("storage_index", 1234)
115 serverA = StubServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
116 serverB = StubServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
117 storage_index = hashutil.storage_index_hash("SI")
118 e0 = ds.add_segment_request(0, now)
120 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
121 e1 = ds.add_segment_request(1, now+2)
123 # two outstanding requests
124 e2 = ds.add_segment_request(2, now+4)
125 e3 = ds.add_segment_request(3, now+5)
126 del e2,e3 # hush pyflakes
128 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
129 e = ds.add_segment_request(4, now)
131 e.deliver(now, 0, 140, 0.5)
133 e = ds.add_dyhb_request(serverA, now)
134 e.finished([1,2], now+1)
135 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
137 e = ds.add_read_event(0, 120, now)
138 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
140 e = ds.add_read_event(120, 30, now+2) # left unfinished
142 e = ds.add_block_request(serverA, 1, 100, 20, now)
143 e.finished(20, now+1)
144 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
146 # make sure that add_read_event() can come first too
147 ds1 = DownloadStatus(storage_index, 1234)
148 e = ds1.add_read_event(0, 120, now)
149 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
155 _all_upload_status = [upload.UploadStatus()]
156 _all_download_status = [build_one_ds()]
157 _all_mapupdate_statuses = [servermap.UpdateStatus()]
158 _all_publish_statuses = [publish.PublishStatus()]
159 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
161 def list_all_upload_statuses(self):
162 return self._all_upload_status
163 def list_all_download_statuses(self):
164 return self._all_download_status
165 def list_all_mapupdate_statuses(self):
166 return self._all_mapupdate_statuses
167 def list_all_publish_statuses(self):
168 return self._all_publish_statuses
169 def list_all_retrieve_statuses(self):
170 return self._all_retrieve_statuses
171 def list_all_helper_statuses(self):
174 class FakeDisplayableServer(StubServer):
175 def __init__(self, serverid, nickname, connected,
176 last_connect_time, last_loss_time, last_rx_time):
177 StubServer.__init__(self, serverid)
178 self.announcement = {"my-version": "allmydata-tahoe-fake",
179 "service-name": "storage",
180 "nickname": nickname}
181 self.connected = connected
182 self.last_loss_time = last_loss_time
183 self.last_rx_time = last_rx_time
184 self.last_connect_time = last_connect_time
185 def is_connected(self):
186 return self.connected
187 def get_permutation_seed(self):
189 def get_remote_host(self):
191 def get_last_loss_time(self):
192 return self.last_loss_time
193 def get_last_received_data_time(self):
194 return self.last_rx_time
195 def get_last_connect_time(self):
196 return self.last_connect_time
197 def get_announcement(self):
198 return self.announcement
199 def get_nickname(self):
200 return self.announcement["nickname"]
201 def get_available_space(self):
204 class FakeBucketCounter(object):
206 return {"last-complete-bucket-count": 0}
207 def get_progress(self):
208 return {"estimated-time-per-cycle": 0,
209 "cycle-in-progress": False,
210 "remaining-wait-time": 0}
212 class FakeLeaseChecker(object):
214 self.expiration_enabled = False
216 self.override_lease_duration = None
217 self.sharetypes_to_expire = {}
219 return {"history": None}
220 def get_progress(self):
221 return {"estimated-time-per-cycle": 0,
222 "cycle-in-progress": False,
223 "remaining-wait-time": 0}
225 class FakeStorageServer(service.MultiService):
227 def __init__(self, nodeid, nickname):
228 service.MultiService.__init__(self)
229 self.my_nodeid = nodeid
230 self.nickname = nickname
231 self.bucket_counter = FakeBucketCounter()
232 self.lease_checker = FakeLeaseChecker()
234 return {"storage_server.accepting_immutable_shares": False}
236 class FakeClient(Client):
238 # don't upcall to Client.__init__, since we only want to initialize a
240 service.MultiService.__init__(self)
241 self.all_contents = {}
242 self.nodeid = "fake_nodeid"
243 self.nickname = u"fake_nickname \u263A"
244 self.introducer_furl = "None"
245 self.stats_provider = FakeStatsProvider()
246 self._secret_holder = SecretHolder("lease secret", "convergence secret")
248 self.convergence = "some random string"
249 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
250 # fake knowledge of another server
251 self.storage_broker.test_add_server("other_nodeid",
252 FakeDisplayableServer(
253 serverid="other_nodeid", nickname=u"other_nickname \u263B", connected = True,
254 last_connect_time = 10, last_loss_time = 20, last_rx_time = 30))
255 self.storage_broker.test_add_server("disconnected_nodeid",
256 FakeDisplayableServer(
257 serverid="other_nodeid", nickname=u"disconnected_nickname \u263B", connected = False,
258 last_connect_time = 15, last_loss_time = 25, last_rx_time = 35))
259 self.introducer_client = None
260 self.history = FakeHistory()
261 self.uploader = FakeUploader()
262 self.uploader.all_contents = self.all_contents
263 self.uploader.setServiceParent(self)
264 self.blacklist = None
265 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
268 self.nodemaker.all_contents = self.all_contents
269 self.mutable_file_default = SDMF_VERSION
270 self.addService(FakeStorageServer(self.nodeid, self.nickname))
272 def get_long_nodeid(self):
274 def get_long_tubid(self):
277 def startService(self):
278 return service.MultiService.startService(self)
279 def stopService(self):
280 return service.MultiService.stopService(self)
282 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
284 class WebMixin(testutil.TimezoneMixin):
286 self.setTimezone('UTC-13:00')
287 self.s = FakeClient()
288 self.s.startService()
289 self.staticdir = self.mktemp()
291 self.fakeTime = 86460 # 1d 0h 1m 0s
292 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
293 clock=self.clock, now_fn=lambda:self.fakeTime)
294 self.ws.setServiceParent(self.s)
295 self.webish_port = self.ws.getPortnum()
296 self.webish_url = self.ws.getURL()
297 assert self.webish_url.endswith("/")
298 self.webish_url = self.webish_url[:-1] # these tests add their own /
300 l = [ self.s.create_dirnode() for x in range(6) ]
301 d = defer.DeferredList(l)
303 self.public_root = res[0][1]
304 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
305 self.public_url = "/uri/" + self.public_root.get_uri()
306 self.private_root = res[1][1]
310 self._foo_uri = foo.get_uri()
311 self._foo_readonly_uri = foo.get_readonly_uri()
312 self._foo_verifycap = foo.get_verify_cap().to_string()
313 # NOTE: we ignore the deferred on all set_uri() calls, because we
314 # know the fake nodes do these synchronously
315 self.public_root.set_uri(u"foo", foo.get_uri(),
316 foo.get_readonly_uri())
318 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
319 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
320 self._bar_txt_verifycap = n.get_verify_cap().to_string()
323 # XXX: Do we ever use this?
324 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
326 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
329 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
330 assert self._quux_txt_uri.startswith("URI:MDMF")
331 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
333 foo.set_uri(u"empty", res[3][1].get_uri(),
334 res[3][1].get_readonly_uri())
335 sub_uri = res[4][1].get_uri()
336 self._sub_uri = sub_uri
337 foo.set_uri(u"sub", sub_uri, sub_uri)
338 sub = self.s.create_node_from_uri(sub_uri)
341 _ign, n, blocking_uri = self.makefile(1)
342 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
344 # filenode to test for html encoding issues
345 self._htmlname_unicode = u"<&weirdly'named\"file>>>_<iframe />.txt"
346 self._htmlname_raw = self._htmlname_unicode.encode('utf-8')
347 self._htmlname_urlencoded = urllib.quote(self._htmlname_raw, '')
348 self._htmlname_escaped = escapeToXML(self._htmlname_raw)
349 self._htmlname_escaped_attr = cgi.escape(self._htmlname_raw, quote=True)
350 self._htmlname_escaped_double = escapeToXML(cgi.escape(self._htmlname_raw, quote=True))
351 self.HTMLNAME_CONTENTS, n, self._htmlname_txt_uri = self.makefile(0)
352 foo.set_uri(self._htmlname_unicode, self._htmlname_txt_uri, self._htmlname_txt_uri)
354 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
355 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
356 # still think of it as an umlaut
357 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
359 self.SUBBAZ_CONTENTS, n, baz_file = self.makefile(2)
360 self._baz_file_uri = baz_file
361 sub.set_uri(u"baz.txt", baz_file, baz_file)
363 _ign, n, self._bad_file_uri = self.makefile(3)
364 # this uri should not be downloadable
365 del self.s.all_contents[self._bad_file_uri]
368 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
369 rodir.get_readonly_uri())
370 rodir.set_uri(u"nor", baz_file, baz_file)
376 # public/foo/quux.txt
377 # public/foo/blockingfile
378 # public/foo/<&weirdly'named\"file>>>_<iframe />.txt
381 # public/foo/sub/baz.txt
383 # public/reedownlee/nor
384 self.NEWFILE_CONTENTS = "newfile contents\n"
386 return foo.get_metadata_for(u"bar.txt")
388 def _got_metadata(metadata):
389 self._bar_txt_metadata = metadata
390 d.addCallback(_got_metadata)
393 def get_all_contents(self):
394 return self.s.all_contents
396 def makefile(self, number):
397 contents = "contents of file %s\n" % number
398 n = create_chk_filenode(contents, self.get_all_contents())
399 return contents, n, n.get_uri()
401 def makefile_mutable(self, number, mdmf=False):
402 contents = "contents of mutable file %s\n" % number
403 n = create_mutable_filenode(contents, mdmf, self.s.all_contents)
404 return contents, n, n.get_uri(), n.get_readonly_uri()
407 return self.s.stopService()
409 def failUnlessIsBarDotTxt(self, res):
410 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
412 def failUnlessIsQuuxDotTxt(self, res):
413 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
415 def failUnlessIsBazDotTxt(self, res):
416 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
418 def failUnlessIsSubBazDotTxt(self, res):
419 self.failUnlessReallyEqual(res, self.SUBBAZ_CONTENTS, res)
421 def failUnlessIsBarJSON(self, res):
422 data = simplejson.loads(res)
423 self.failUnless(isinstance(data, list))
424 self.failUnlessEqual(data[0], "filenode")
425 self.failUnless(isinstance(data[1], dict))
426 self.failIf(data[1]["mutable"])
427 self.failIfIn("rw_uri", data[1]) # immutable
428 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
429 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
430 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
432 def failUnlessIsQuuxJSON(self, res, readonly=False):
433 data = simplejson.loads(res)
434 self.failUnless(isinstance(data, list))
435 self.failUnlessEqual(data[0], "filenode")
436 self.failUnless(isinstance(data[1], dict))
438 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
440 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
441 self.failUnless(metadata['mutable'])
443 self.failIfIn("rw_uri", metadata)
445 self.failUnlessIn("rw_uri", metadata)
446 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
447 self.failUnlessIn("ro_uri", metadata)
448 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
449 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
451 def failUnlessIsFooJSON(self, res):
452 data = simplejson.loads(res)
453 self.failUnless(isinstance(data, list))
454 self.failUnlessEqual(data[0], "dirnode", res)
455 self.failUnless(isinstance(data[1], dict))
456 self.failUnless(data[1]["mutable"])
457 self.failUnlessIn("rw_uri", data[1]) # mutable
458 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
459 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
460 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
462 kidnames = sorted([unicode(n) for n in data[1]["children"]])
463 self.failUnlessEqual(kidnames,
464 [self._htmlname_unicode, u"bar.txt", u"baz.txt",
465 u"blockingfile", u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
466 kids = dict( [(unicode(name),value)
468 in data[1]["children"].iteritems()] )
469 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
470 self.failUnlessIn("metadata", kids[u"sub"][1])
471 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
472 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
473 self.failUnlessIn("linkcrtime", tahoe_md)
474 self.failUnlessIn("linkmotime", tahoe_md)
475 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
476 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
477 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
478 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
479 self._bar_txt_verifycap)
480 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
481 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
482 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
483 self._bar_txt_metadata["tahoe"]["linkcrtime"])
484 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
486 self.failUnlessIn("quux.txt", kids)
487 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
489 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
490 self._quux_txt_readonly_uri)
492 def GET(self, urlpath, followRedirect=False, return_response=False,
494 # if return_response=True, this fires with (data, statuscode,
495 # respheaders) instead of just data.
496 assert not isinstance(urlpath, unicode)
497 url = self.webish_url + urlpath
498 factory = HTTPClientGETFactory(url, method="GET",
499 followRedirect=followRedirect, **kwargs)
500 reactor.connectTCP("localhost", self.webish_port, factory)
503 return (data, factory.status, factory.response_headers)
505 d.addCallback(_got_data)
506 return factory.deferred
508 def HEAD(self, urlpath, return_response=False, **kwargs):
509 # this requires some surgery, because twisted.web.client doesn't want
510 # to give us back the response headers.
511 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
512 reactor.connectTCP("localhost", self.webish_port, factory)
515 return (data, factory.status, factory.response_headers)
517 d.addCallback(_got_data)
518 return factory.deferred
520 def PUT(self, urlpath, data, **kwargs):
521 url = self.webish_url + urlpath
522 return client.getPage(url, method="PUT", postdata=data, **kwargs)
524 def DELETE(self, urlpath):
525 url = self.webish_url + urlpath
526 return client.getPage(url, method="DELETE")
528 def POST(self, urlpath, followRedirect=False, **fields):
529 sepbase = "boogabooga"
533 form.append('Content-Disposition: form-data; name="_charset"')
537 for name, value in fields.iteritems():
538 if isinstance(value, tuple):
539 filename, value = value
540 form.append('Content-Disposition: form-data; name="%s"; '
541 'filename="%s"' % (name, filename.encode("utf-8")))
543 form.append('Content-Disposition: form-data; name="%s"' % name)
545 if isinstance(value, unicode):
546 value = value.encode("utf-8")
549 assert isinstance(value, str)
556 body = "\r\n".join(form) + "\r\n"
557 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
558 return self.POST2(urlpath, body, headers, followRedirect)
560 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
561 url = self.webish_url + urlpath
562 return client.getPage(url, method="POST", postdata=body,
563 headers=headers, followRedirect=followRedirect)
565 def shouldFail(self, res, expected_failure, which,
566 substring=None, response_substring=None):
567 if isinstance(res, failure.Failure):
568 res.trap(expected_failure)
570 self.failUnlessIn(substring, str(res), which)
571 if response_substring:
572 self.failUnlessIn(response_substring, res.value.response, which)
574 self.fail("%s was supposed to raise %s, not get '%s'" %
575 (which, expected_failure, res))
577 def shouldFail2(self, expected_failure, which, substring,
579 callable, *args, **kwargs):
580 assert substring is None or isinstance(substring, str)
581 assert response_substring is None or isinstance(response_substring, str)
582 d = defer.maybeDeferred(callable, *args, **kwargs)
584 if isinstance(res, failure.Failure):
585 res.trap(expected_failure)
587 self.failUnlessIn(substring, str(res),
588 "'%s' not in '%s' (response is '%s') for test '%s'" % \
589 (substring, str(res),
590 getattr(res.value, "response", ""),
592 if response_substring:
593 self.failUnlessIn(response_substring, res.value.response,
594 "'%s' not in '%s' for test '%s'" % \
595 (response_substring, res.value.response,
598 self.fail("%s was supposed to raise %s, not get '%s'" %
599 (which, expected_failure, res))
603 def should404(self, res, which):
604 if isinstance(res, failure.Failure):
605 res.trap(error.Error)
606 self.failUnlessReallyEqual(res.value.status, "404")
608 self.fail("%s was supposed to Error(404), not get '%s'" %
611 def should302(self, res, which):
612 if isinstance(res, failure.Failure):
613 res.trap(error.Error)
614 self.failUnlessReallyEqual(res.value.status, "302")
616 self.fail("%s was supposed to Error(302), not get '%s'" %
619 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
620 def test_create(self):
623 def test_welcome(self):
626 self.failUnlessIn('<title>Tahoe-LAFS - Welcome</title>', res)
627 self.failUnlessIn(FAVICON_MARKUP, res)
628 self.failUnlessIn('<a href="status">Recent and Active Operations</a>', res)
629 self.failUnlessIn('<a href="statistics">Operational Statistics</a>', res)
630 self.failUnlessIn('<input type="hidden" name="t" value="report-incident" />', res)
631 self.failUnlessIn('Page rendered at', res)
632 self.failUnlessIn('Tahoe-LAFS code imported from:', res)
633 res_u = res.decode('utf-8')
634 self.failUnlessIn(u'<td>fake_nickname \u263A</td>', res_u)
635 self.failUnlessIn(u'<div class="nickname">other_nickname \u263B</div>', res_u)
636 self.failUnlessIn(u'Connected to <span>1</span>\n of <span>2</span> known storage servers', res_u)
637 self.failUnlessIn(u'<div class="status-indicator"><img src="img/connected-yes.png" alt="Connected" /></div>\n <a class="timestamp" title="1970-01-01 13:00:10">1d\u00A00h\u00A00m\u00A050s</a>', res_u)
638 self.failUnlessIn(u'<div class="status-indicator"><img src="img/connected-no.png" alt="Disconnected" /></div>\n <a class="timestamp" title="1970-01-01 13:00:25">1d\u00A00h\u00A00m\u00A035s</a>', res_u)
639 self.failUnlessIn(u'<td class="service-last-received-data"><a class="timestamp" title="1970-01-01 13:00:30">1d\u00A00h\u00A00m\u00A030s</a></td>', res_u)
640 self.failUnlessIn(u'<td class="service-last-received-data"><a class="timestamp" title="1970-01-01 13:00:35">1d\u00A00h\u00A00m\u00A025s</a></td>', res_u)
641 self.failUnlessIn(u'\u00A9 <a href="https://tahoe-lafs.org/">Tahoe-LAFS Software Foundation', res_u)
642 self.failUnlessIn('<td><h3>Available</h3></td>', res)
643 self.failUnlessIn('123.5kB', res)
645 self.s.basedir = 'web/test_welcome'
646 fileutil.make_dirs("web/test_welcome")
647 fileutil.make_dirs("web/test_welcome/private")
649 d.addCallback(_check)
652 def test_introducer_status(self):
653 class MockIntroducerClient(object):
654 def __init__(self, connected):
655 self.connected = connected
656 def connected_to_introducer(self):
657 return self.connected
659 d = defer.succeed(None)
661 # introducer not connected, unguessable furl
662 def _set_introducer_not_connected_unguessable(ign):
663 self.s.introducer_furl = "pb://someIntroducer/secret"
664 self.s.introducer_client = MockIntroducerClient(False)
666 d.addCallback(_set_introducer_not_connected_unguessable)
667 def _check_introducer_not_connected_unguessable(res):
668 html = res.replace('\n', ' ')
669 self.failUnlessIn('<div class="furl">pb://someIntroducer/[censored]</div>', html)
670 self.failIfIn('pb://someIntroducer/secret', html)
671 self.failUnless(re.search('<img src="img/connected-no.png" alt="Disconnected" />', html), res)
672 d.addCallback(_check_introducer_not_connected_unguessable)
674 # introducer connected, unguessable furl
675 def _set_introducer_connected_unguessable(ign):
676 self.s.introducer_furl = "pb://someIntroducer/secret"
677 self.s.introducer_client = MockIntroducerClient(True)
679 d.addCallback(_set_introducer_connected_unguessable)
680 def _check_introducer_connected_unguessable(res):
681 html = res.replace('\n', ' ')
682 self.failUnlessIn('<div class="furl">pb://someIntroducer/[censored]</div>', html)
683 self.failIfIn('pb://someIntroducer/secret', html)
684 self.failUnless(re.search('<img src="img/connected-yes.png" alt="Connected" />', html), res)
685 d.addCallback(_check_introducer_connected_unguessable)
687 # introducer connected, guessable furl
688 def _set_introducer_connected_guessable(ign):
689 self.s.introducer_furl = "pb://someIntroducer/introducer"
690 self.s.introducer_client = MockIntroducerClient(True)
692 d.addCallback(_set_introducer_connected_guessable)
693 def _check_introducer_connected_guessable(res):
694 html = res.replace('\n', ' ')
695 self.failUnlessIn('<div class="furl">pb://someIntroducer/introducer</div>', html)
696 self.failUnless(re.search('<img src="img/connected-yes.png" alt="Connected" />', html), res)
697 d.addCallback(_check_introducer_connected_guessable)
700 def test_helper_status(self):
701 d = defer.succeed(None)
703 # set helper furl to None
704 def _set_no_helper(ign):
705 self.s.uploader.helper_furl = None
707 d.addCallback(_set_no_helper)
708 def _check_no_helper(res):
709 html = res.replace('\n', ' ')
710 self.failUnless(re.search('<img src="img/connected-not-configured.png" alt="Not Configured" />', html), res)
711 d.addCallback(_check_no_helper)
713 # enable helper, not connected
714 def _set_helper_not_connected(ign):
715 self.s.uploader.helper_furl = "pb://someHelper/secret"
716 self.s.uploader.helper_connected = False
718 d.addCallback(_set_helper_not_connected)
719 def _check_helper_not_connected(res):
720 html = res.replace('\n', ' ')
721 self.failUnlessIn('<div class="furl">pb://someHelper/[censored]</div>', html)
722 self.failIfIn('pb://someHelper/secret', html)
723 self.failUnless(re.search('<img src="img/connected-no.png" alt="Disconnected" />', html), res)
724 d.addCallback(_check_helper_not_connected)
726 # enable helper, connected
727 def _set_helper_connected(ign):
728 self.s.uploader.helper_furl = "pb://someHelper/secret"
729 self.s.uploader.helper_connected = True
731 d.addCallback(_set_helper_connected)
732 def _check_helper_connected(res):
733 html = res.replace('\n', ' ')
734 self.failUnlessIn('<div class="furl">pb://someHelper/[censored]</div>', html)
735 self.failIfIn('pb://someHelper/secret', html)
736 self.failUnless(re.search('<img src="img/connected-yes.png" alt="Connected" />', html), res)
737 d.addCallback(_check_helper_connected)
740 def test_storage(self):
741 d = self.GET("/storage")
743 self.failUnlessIn('Storage Server Status', res)
744 self.failUnlessIn(FAVICON_MARKUP, res)
745 res_u = res.decode('utf-8')
746 self.failUnlessIn(u'<li>Server Nickname: <span class="nickname mine">fake_nickname \u263A</span></li>', res_u)
747 d.addCallback(_check)
750 def test_status(self):
751 h = self.s.get_history()
752 dl_num = h.list_all_download_statuses()[0].get_counter()
753 ul_num = h.list_all_upload_statuses()[0].get_counter()
754 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
755 pub_num = h.list_all_publish_statuses()[0].get_counter()
756 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
757 d = self.GET("/status", followRedirect=True)
759 self.failUnlessIn('Recent and Active Operations', res)
760 self.failUnlessIn('"down-%d"' % dl_num, res)
761 self.failUnlessIn('"up-%d"' % ul_num, res)
762 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
763 self.failUnlessIn('"publish-%d"' % pub_num, res)
764 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
765 d.addCallback(_check)
766 d.addCallback(lambda res: self.GET("/status/?t=json"))
767 def _check_json(res):
768 data = simplejson.loads(res)
769 self.failUnless(isinstance(data, dict))
770 #active = data["active"]
771 # TODO: test more. We need a way to fake an active operation
773 d.addCallback(_check_json)
775 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
777 self.failUnlessIn("File Download Status", res)
778 d.addCallback(_check_dl)
779 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
780 def _check_dl_json(res):
781 data = simplejson.loads(res)
782 self.failUnless(isinstance(data, dict))
783 self.failUnlessIn("read", data)
784 self.failUnlessEqual(data["read"][0]["length"], 120)
785 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
786 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
787 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
788 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
789 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
790 # serverids[] keys are strings, since that's what JSON does, but
791 # we'd really like them to be ints
792 self.failUnlessEqual(data["serverids"]["0"], "phwrsjte")
793 self.failUnless(data["serverids"].has_key("1"),
794 str(data["serverids"]))
795 self.failUnlessEqual(data["serverids"]["1"], "cmpuvkjm",
796 str(data["serverids"]))
797 self.failUnlessEqual(data["server_info"][phwr_id]["short"],
799 self.failUnlessEqual(data["server_info"][cmpu_id]["short"],
801 self.failUnlessIn("dyhb", data)
802 self.failUnlessIn("misc", data)
803 d.addCallback(_check_dl_json)
804 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
806 self.failUnlessIn("File Upload Status", res)
807 d.addCallback(_check_ul)
808 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
809 def _check_mapupdate(res):
810 self.failUnlessIn("Mutable File Servermap Update Status", res)
811 d.addCallback(_check_mapupdate)
812 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
813 def _check_publish(res):
814 self.failUnlessIn("Mutable File Publish Status", res)
815 d.addCallback(_check_publish)
816 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
817 def _check_retrieve(res):
818 self.failUnlessIn("Mutable File Retrieve Status", res)
819 d.addCallback(_check_retrieve)
823 def test_status_numbers(self):
824 drrm = status.DownloadResultsRendererMixin()
825 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
826 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
827 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
828 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
829 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
830 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
831 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
832 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
833 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
835 urrm = status.UploadResultsRendererMixin()
836 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
837 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
838 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
839 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
840 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
841 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
842 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
843 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
844 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
846 def test_GET_FILEURL(self):
847 d = self.GET(self.public_url + "/foo/bar.txt")
848 d.addCallback(self.failUnlessIsBarDotTxt)
851 def test_GET_FILEURL_range(self):
852 headers = {"range": "bytes=1-10"}
853 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
854 return_response=True)
855 def _got((res, status, headers)):
856 self.failUnlessReallyEqual(int(status), 206)
857 self.failUnless(headers.has_key("content-range"))
858 self.failUnlessReallyEqual(headers["content-range"][0],
859 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
860 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
864 def test_GET_FILEURL_partial_range(self):
865 headers = {"range": "bytes=5-"}
866 length = len(self.BAR_CONTENTS)
867 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
868 return_response=True)
869 def _got((res, status, headers)):
870 self.failUnlessReallyEqual(int(status), 206)
871 self.failUnless(headers.has_key("content-range"))
872 self.failUnlessReallyEqual(headers["content-range"][0],
873 "bytes 5-%d/%d" % (length-1, length))
874 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
878 def test_GET_FILEURL_partial_end_range(self):
879 headers = {"range": "bytes=-5"}
880 length = len(self.BAR_CONTENTS)
881 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
882 return_response=True)
883 def _got((res, status, headers)):
884 self.failUnlessReallyEqual(int(status), 206)
885 self.failUnless(headers.has_key("content-range"))
886 self.failUnlessReallyEqual(headers["content-range"][0],
887 "bytes %d-%d/%d" % (length-5, length-1, length))
888 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
892 def test_GET_FILEURL_partial_range_overrun(self):
893 headers = {"range": "bytes=100-200"}
894 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
895 "416 Requested Range not satisfiable",
896 "First beyond end of file",
897 self.GET, self.public_url + "/foo/bar.txt",
901 def test_HEAD_FILEURL_range(self):
902 headers = {"range": "bytes=1-10"}
903 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
904 return_response=True)
905 def _got((res, status, headers)):
906 self.failUnlessReallyEqual(res, "")
907 self.failUnlessReallyEqual(int(status), 206)
908 self.failUnless(headers.has_key("content-range"))
909 self.failUnlessReallyEqual(headers["content-range"][0],
910 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
914 def test_HEAD_FILEURL_partial_range(self):
915 headers = {"range": "bytes=5-"}
916 length = len(self.BAR_CONTENTS)
917 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
918 return_response=True)
919 def _got((res, status, headers)):
920 self.failUnlessReallyEqual(int(status), 206)
921 self.failUnless(headers.has_key("content-range"))
922 self.failUnlessReallyEqual(headers["content-range"][0],
923 "bytes 5-%d/%d" % (length-1, length))
927 def test_HEAD_FILEURL_partial_end_range(self):
928 headers = {"range": "bytes=-5"}
929 length = len(self.BAR_CONTENTS)
930 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
931 return_response=True)
932 def _got((res, status, headers)):
933 self.failUnlessReallyEqual(int(status), 206)
934 self.failUnless(headers.has_key("content-range"))
935 self.failUnlessReallyEqual(headers["content-range"][0],
936 "bytes %d-%d/%d" % (length-5, length-1, length))
940 def test_HEAD_FILEURL_partial_range_overrun(self):
941 headers = {"range": "bytes=100-200"}
942 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
943 "416 Requested Range not satisfiable",
945 self.HEAD, self.public_url + "/foo/bar.txt",
949 def test_GET_FILEURL_range_bad(self):
950 headers = {"range": "BOGUS=fizbop-quarnak"}
951 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
952 return_response=True)
953 def _got((res, status, headers)):
954 self.failUnlessReallyEqual(int(status), 200)
955 self.failUnless(not headers.has_key("content-range"))
956 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
960 def test_HEAD_FILEURL(self):
961 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
962 def _got((res, status, headers)):
963 self.failUnlessReallyEqual(res, "")
964 self.failUnlessReallyEqual(headers["content-length"][0],
965 str(len(self.BAR_CONTENTS)))
966 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
970 def test_GET_FILEURL_named(self):
971 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
972 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
973 d = self.GET(base + "/@@name=/blah.txt")
974 d.addCallback(self.failUnlessIsBarDotTxt)
975 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
976 d.addCallback(self.failUnlessIsBarDotTxt)
977 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
978 d.addCallback(self.failUnlessIsBarDotTxt)
979 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
980 d.addCallback(self.failUnlessIsBarDotTxt)
981 save_url = base + "?save=true&filename=blah.txt"
982 d.addCallback(lambda res: self.GET(save_url))
983 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
984 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
985 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
986 u_url = base + "?save=true&filename=" + u_fn_e
987 d.addCallback(lambda res: self.GET(u_url))
988 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
991 def test_PUT_FILEURL_named_bad(self):
992 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
993 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
995 "/file can only be used with GET or HEAD",
996 self.PUT, base + "/@@name=/blah.txt", "")
1000 def test_GET_DIRURL_named_bad(self):
1001 base = "/file/%s" % urllib.quote(self._foo_uri)
1002 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
1004 "is not a file-cap",
1005 self.GET, base + "/@@name=/blah.txt")
1008 def test_GET_slash_file_bad(self):
1009 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
1011 "/file must be followed by a file-cap and a name",
1015 def test_GET_unhandled_URI_named(self):
1016 contents, n, newuri = self.makefile(12)
1017 verifier_cap = n.get_verify_cap().to_string()
1018 base = "/file/%s" % urllib.quote(verifier_cap)
1019 # client.create_node_from_uri() can't handle verify-caps
1020 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
1021 "400 Bad Request", "is not a file-cap",
1025 def test_GET_unhandled_URI(self):
1026 contents, n, newuri = self.makefile(12)
1027 verifier_cap = n.get_verify_cap().to_string()
1028 base = "/uri/%s" % urllib.quote(verifier_cap)
1029 # client.create_node_from_uri() can't handle verify-caps
1030 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
1032 "GET unknown URI type: can only do t=info",
1036 def test_GET_FILE_URI(self):
1037 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
1039 d.addCallback(self.failUnlessIsBarDotTxt)
1042 def test_GET_FILE_URI_mdmf(self):
1043 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
1045 d.addCallback(self.failUnlessIsQuuxDotTxt)
1048 def test_GET_FILE_URI_mdmf_extensions(self):
1049 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
1051 d.addCallback(self.failUnlessIsQuuxDotTxt)
1054 def test_GET_FILE_URI_mdmf_readonly(self):
1055 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
1057 d.addCallback(self.failUnlessIsQuuxDotTxt)
1060 def test_GET_FILE_URI_badchild(self):
1061 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
1062 errmsg = "Files have no children, certainly not named 'boguschild'"
1063 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
1064 "400 Bad Request", errmsg,
1068 def test_PUT_FILE_URI_badchild(self):
1069 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
1070 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
1071 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
1072 "400 Bad Request", errmsg,
1076 def test_PUT_FILE_URI_mdmf(self):
1077 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
1078 self._quux_new_contents = "new_contents"
1080 d.addCallback(lambda res:
1081 self.failUnlessIsQuuxDotTxt(res))
1082 d.addCallback(lambda ignored:
1083 self.PUT(base, self._quux_new_contents))
1084 d.addCallback(lambda ignored:
1086 d.addCallback(lambda res:
1087 self.failUnlessReallyEqual(res, self._quux_new_contents))
1090 def test_PUT_FILE_URI_mdmf_extensions(self):
1091 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
1092 self._quux_new_contents = "new_contents"
1094 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
1095 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
1096 d.addCallback(lambda ignored: self.GET(base))
1097 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
1101 def test_PUT_FILE_URI_mdmf_readonly(self):
1102 # We're not allowed to PUT things to a readonly cap.
1103 base = "/uri/%s" % self._quux_txt_readonly_uri
1105 d.addCallback(lambda res:
1106 self.failUnlessIsQuuxDotTxt(res))
1107 # What should we get here? We get a 500 error now; that's not right.
1108 d.addCallback(lambda ignored:
1109 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
1110 "400 Bad Request", "read-only cap",
1111 self.PUT, base, "new data"))
1114 def test_PUT_FILE_URI_sdmf_readonly(self):
1115 # We're not allowed to put things to a readonly cap.
1116 base = "/uri/%s" % self._baz_txt_readonly_uri
1118 d.addCallback(lambda res:
1119 self.failUnlessIsBazDotTxt(res))
1120 d.addCallback(lambda ignored:
1121 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
1122 "400 Bad Request", "read-only cap",
1123 self.PUT, base, "new_data"))
1126 def test_GET_etags(self):
1128 def _check_etags(uri):
1130 d2 = _get_etag(uri, 'json')
1131 d = defer.DeferredList([d1, d2], consumeErrors=True)
1132 def _check(results):
1133 # All deferred must succeed
1134 self.failUnless(all([r[0] for r in results]))
1135 # the etag for the t=json form should be just like the etag
1136 # fo the default t='' form, but with a 'json' suffix
1137 self.failUnlessEqual(results[0][1] + 'json', results[1][1])
1138 d.addCallback(_check)
1141 def _get_etag(uri, t=''):
1142 targetbase = "/uri/%s?t=%s" % (urllib.quote(uri.strip()), t)
1143 d = self.GET(targetbase, return_response=True, followRedirect=True)
1144 def _just_the_etag(result):
1145 data, response, headers = result
1146 etag = headers['etag'][0]
1147 if uri.startswith('URI:DIR'):
1148 self.failUnless(etag.startswith('DIR:'), etag)
1150 return d.addCallback(_just_the_etag)
1152 # Check that etags work with immutable directories
1153 (newkids, caps) = self._create_immutable_children()
1154 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1155 simplejson.dumps(newkids))
1156 def _stash_immdir_uri(uri):
1157 self._immdir_uri = uri
1159 d.addCallback(_stash_immdir_uri)
1160 d.addCallback(_check_etags)
1162 # Check that etags work with immutable files
1163 d.addCallback(lambda _: _check_etags(self._bar_txt_uri))
1165 # use the ETag on GET
1166 def _check_match(ign):
1167 uri = "/uri/%s" % self._bar_txt_uri
1168 d = self.GET(uri, return_response=True)
1170 d.addCallback(lambda (data, code, headers):
1172 # do a GET that's supposed to match the ETag
1173 d.addCallback(lambda etag:
1174 self.GET(uri, return_response=True,
1175 headers={"If-None-Match": etag}))
1176 # make sure it short-circuited (304 instead of 200)
1177 d.addCallback(lambda (data, code, headers):
1178 self.failUnlessEqual(int(code), http.NOT_MODIFIED))
1180 d.addCallback(_check_match)
1182 def _no_etag(uri, t):
1183 target = "/uri/%s?t=%s" % (uri, t)
1184 d = self.GET(target, return_response=True, followRedirect=True)
1185 d.addCallback(lambda (data, code, headers):
1186 self.failIf("etag" in headers, target))
1188 def _yes_etag(uri, t):
1189 target = "/uri/%s?t=%s" % (uri, t)
1190 d = self.GET(target, return_response=True, followRedirect=True)
1191 d.addCallback(lambda (data, code, headers):
1192 self.failUnless("etag" in headers, target))
1195 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, ""))
1196 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "json"))
1197 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "uri"))
1198 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "readonly-uri"))
1199 d.addCallback(lambda ign: _no_etag(self._bar_txt_uri, "info"))
1201 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, ""))
1202 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "json"))
1203 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "uri"))
1204 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "readonly-uri"))
1205 d.addCallback(lambda ign: _no_etag(self._immdir_uri, "info"))
1206 d.addCallback(lambda ign: _no_etag(self._immdir_uri, "rename-form"))
1210 # TODO: version of this with a Unicode filename
1211 def test_GET_FILEURL_save(self):
1212 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
1213 return_response=True)
1214 def _got((res, statuscode, headers)):
1215 content_disposition = headers["content-disposition"][0]
1216 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
1217 self.failUnlessIsBarDotTxt(res)
1221 def test_GET_FILEURL_missing(self):
1222 d = self.GET(self.public_url + "/foo/missing")
1223 d.addBoth(self.should404, "test_GET_FILEURL_missing")
1226 def test_GET_FILEURL_info_mdmf(self):
1227 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
1229 self.failUnlessIn("mutable file (mdmf)", res)
1230 self.failUnlessIn(self._quux_txt_uri, res)
1231 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1235 def test_GET_FILEURL_info_mdmf_readonly(self):
1236 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
1238 self.failUnlessIn("mutable file (mdmf)", res)
1239 self.failIfIn(self._quux_txt_uri, res)
1240 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1244 def test_GET_FILEURL_info_sdmf(self):
1245 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
1247 self.failUnlessIn("mutable file (sdmf)", res)
1248 self.failUnlessIn(self._baz_txt_uri, res)
1252 def test_GET_FILEURL_info_mdmf_extensions(self):
1253 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
1255 self.failUnlessIn("mutable file (mdmf)", res)
1256 self.failUnlessIn(self._quux_txt_uri, res)
1257 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1261 def test_PUT_overwrite_only_files(self):
1262 # create a directory, put a file in that directory.
1263 contents, n, filecap = self.makefile(8)
1264 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1265 d.addCallback(lambda res:
1266 self.PUT(self.public_url + "/foo/dir/file1.txt",
1267 self.NEWFILE_CONTENTS))
1268 # try to overwrite the file with replace=only-files
1269 # (this should work)
1270 d.addCallback(lambda res:
1271 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1273 d.addCallback(lambda res:
1274 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1275 "There was already a child by that name, and you asked me "
1276 "to not replace it",
1277 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
1281 def test_PUT_NEWFILEURL(self):
1282 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1283 # TODO: we lose the response code, so we can't check this
1284 #self.failUnlessReallyEqual(responsecode, 201)
1285 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1286 d.addCallback(lambda res:
1287 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1288 self.NEWFILE_CONTENTS))
1291 def test_PUT_NEWFILEURL_not_mutable(self):
1292 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1293 self.NEWFILE_CONTENTS)
1294 # TODO: we lose the response code, so we can't check this
1295 #self.failUnlessReallyEqual(responsecode, 201)
1296 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1297 d.addCallback(lambda res:
1298 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1299 self.NEWFILE_CONTENTS))
1302 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
1303 # this should get us a few segments of an MDMF mutable file,
1304 # which we can then test for.
1305 contents = self.NEWFILE_CONTENTS * 300000
1306 d = self.PUT("/uri?format=mdmf",
1308 def _got_filecap(filecap):
1309 self.failUnless(filecap.startswith("URI:MDMF"))
1311 d.addCallback(_got_filecap)
1312 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1313 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1316 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1317 contents = self.NEWFILE_CONTENTS * 300000
1318 d = self.PUT("/uri?format=sdmf",
1320 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1321 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1324 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1325 contents = self.NEWFILE_CONTENTS * 300000
1326 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1327 400, "Bad Request", "Unknown format: foo",
1328 self.PUT, "/uri?format=foo",
1331 def test_PUT_NEWFILEURL_range_bad(self):
1332 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1333 target = self.public_url + "/foo/new.txt"
1334 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1335 "501 Not Implemented",
1336 "Content-Range in PUT not yet supported",
1337 # (and certainly not for immutable files)
1338 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1340 d.addCallback(lambda res:
1341 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1344 def test_PUT_NEWFILEURL_mutable(self):
1345 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1346 self.NEWFILE_CONTENTS)
1347 # TODO: we lose the response code, so we can't check this
1348 #self.failUnlessReallyEqual(responsecode, 201)
1349 def _check_uri(res):
1350 u = uri.from_string_mutable_filenode(res)
1351 self.failUnless(u.is_mutable())
1352 self.failIf(u.is_readonly())
1354 d.addCallback(_check_uri)
1355 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1356 d.addCallback(lambda res:
1357 self.failUnlessMutableChildContentsAre(self._foo_node,
1359 self.NEWFILE_CONTENTS))
1362 def test_PUT_NEWFILEURL_mutable_toobig(self):
1363 # It is okay to upload large mutable files, so we should be able
1365 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1366 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1369 def test_PUT_NEWFILEURL_replace(self):
1370 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1371 # TODO: we lose the response code, so we can't check this
1372 #self.failUnlessReallyEqual(responsecode, 200)
1373 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1374 d.addCallback(lambda res:
1375 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1376 self.NEWFILE_CONTENTS))
1379 def test_PUT_NEWFILEURL_bad_t(self):
1380 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1381 "PUT to a file: bad t=bogus",
1382 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1386 def test_PUT_NEWFILEURL_no_replace(self):
1387 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1388 self.NEWFILE_CONTENTS)
1389 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1391 "There was already a child by that name, and you asked me "
1392 "to not replace it")
1395 def test_PUT_NEWFILEURL_mkdirs(self):
1396 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1398 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1399 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1400 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1401 d.addCallback(lambda res:
1402 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1403 self.NEWFILE_CONTENTS))
1406 def test_PUT_NEWFILEURL_blocked(self):
1407 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1408 self.NEWFILE_CONTENTS)
1409 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1411 "Unable to create directory 'blockingfile': a file was in the way")
1414 def test_PUT_NEWFILEURL_emptyname(self):
1415 # an empty pathname component (i.e. a double-slash) is disallowed
1416 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1418 "The webapi does not allow empty pathname components",
1419 self.PUT, self.public_url + "/foo//new.txt", "")
1422 def test_DELETE_FILEURL(self):
1423 d = self.DELETE(self.public_url + "/foo/bar.txt")
1424 d.addCallback(lambda res:
1425 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1428 def test_DELETE_FILEURL_missing(self):
1429 d = self.DELETE(self.public_url + "/foo/missing")
1430 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1433 def test_DELETE_FILEURL_missing2(self):
1434 d = self.DELETE(self.public_url + "/missing/missing")
1435 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1438 def failUnlessHasBarDotTxtMetadata(self, res):
1439 data = simplejson.loads(res)
1440 self.failUnless(isinstance(data, list))
1441 self.failUnlessIn("metadata", data[1])
1442 self.failUnlessIn("tahoe", data[1]["metadata"])
1443 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1444 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1445 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1446 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1448 def test_GET_FILEURL_json(self):
1449 # twisted.web.http.parse_qs ignores any query args without an '=', so
1450 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1451 # instead. This may make it tricky to emulate the S3 interface
1453 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1455 self.failUnlessIsBarJSON(data)
1456 self.failUnlessHasBarDotTxtMetadata(data)
1458 d.addCallback(_check1)
1461 def test_GET_FILEURL_json_mutable_type(self):
1462 # The JSON should include format, which says whether the
1463 # file is SDMF or MDMF
1464 d = self.PUT("/uri?format=mdmf",
1465 self.NEWFILE_CONTENTS * 300000)
1466 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1467 def _got_json(json, version):
1468 data = simplejson.loads(json)
1469 assert "filenode" == data[0]
1471 assert isinstance(data, dict)
1473 self.failUnlessIn("format", data)
1474 self.failUnlessEqual(data["format"], version)
1476 d.addCallback(_got_json, "MDMF")
1477 # Now make an SDMF file and check that it is reported correctly.
1478 d.addCallback(lambda ignored:
1479 self.PUT("/uri?format=sdmf",
1480 self.NEWFILE_CONTENTS * 300000))
1481 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1482 d.addCallback(_got_json, "SDMF")
1485 def test_GET_FILEURL_json_mdmf(self):
1486 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1487 d.addCallback(self.failUnlessIsQuuxJSON)
1490 def test_GET_FILEURL_json_missing(self):
1491 d = self.GET(self.public_url + "/foo/missing?json")
1492 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1495 def test_GET_FILEURL_uri(self):
1496 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1498 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1499 d.addCallback(_check)
1500 d.addCallback(lambda res:
1501 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1503 # for now, for files, uris and readonly-uris are the same
1504 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1505 d.addCallback(_check2)
1508 def test_GET_FILEURL_badtype(self):
1509 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1512 self.public_url + "/foo/bar.txt?t=bogus")
1515 def test_CSS_FILE(self):
1516 d = self.GET("/tahoe.css", followRedirect=True)
1518 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1519 self.failUnless(CSS_STYLE.search(res), res)
1520 d.addCallback(_check)
1523 def test_GET_FILEURL_uri_missing(self):
1524 d = self.GET(self.public_url + "/foo/missing?t=uri")
1525 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1528 def _check_upload_and_mkdir_forms(self, html):
1529 # We should have a form to create a file, with radio buttons that allow
1530 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1531 self.failUnlessIn('name="t" value="upload"', html)
1532 self.failUnless(re.search('<input [^/]*id="upload-chk"', html), html)
1533 self.failUnless(re.search('<input [^/]*id="upload-sdmf"', html), html)
1534 self.failUnless(re.search('<input [^/]*id="upload-mdmf"', html), html)
1536 # We should also have the ability to create a mutable directory, with
1537 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1538 # or MDMF directory.
1539 self.failUnlessIn('name="t" value="mkdir"', html)
1540 self.failUnless(re.search('<input [^/]*id="mkdir-sdmf"', html), html)
1541 self.failUnless(re.search('<input [^/]*id="mkdir-mdmf"', html), html)
1543 self.failUnlessIn(FAVICON_MARKUP, html)
1545 def test_GET_DIRECTORY_html(self):
1546 d = self.GET(self.public_url + "/foo", followRedirect=True)
1548 self.failUnlessIn('<li class="toolbar-item"><a href="../../..">Return to Welcome page</a></li>', html)
1549 self._check_upload_and_mkdir_forms(html)
1550 self.failUnlessIn("quux", html)
1551 d.addCallback(_check)
1554 def test_GET_DIRECTORY_html_filenode_encoding(self):
1555 d = self.GET(self.public_url + "/foo", followRedirect=True)
1557 # Check if encoded entries are there
1558 self.failUnlessIn('@@named=/' + self._htmlname_urlencoded + '">'
1559 + self._htmlname_escaped + '</a>', html)
1560 self.failUnlessIn('value="' + self._htmlname_escaped_attr + '"', html)
1561 self.failIfIn(self._htmlname_escaped_double, html)
1562 # Make sure that Nevow escaping actually works by checking for unsafe characters
1563 # and that '&' is escaped.
1565 self.failUnlessIn(entity, self._htmlname_raw)
1566 self.failIfIn(entity, self._htmlname_escaped)
1567 self.failUnlessIn('&', re.sub(r'&(amp|lt|gt|quot|apos);', '', self._htmlname_raw))
1568 self.failIfIn('&', re.sub(r'&(amp|lt|gt|quot|apos);', '', self._htmlname_escaped))
1569 d.addCallback(_check)
1572 def test_GET_root_html(self):
1574 d.addCallback(self._check_upload_and_mkdir_forms)
1577 def test_GET_DIRURL(self):
1578 # the addSlash means we get a redirect here
1579 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1581 d = self.GET(self.public_url + "/foo", followRedirect=True)
1583 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1585 # the FILE reference points to a URI, but it should end in bar.txt
1586 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1587 (ROOT, urllib.quote(self._bar_txt_uri)))
1588 get_bar = "".join([r'<td>FILE</td>',
1590 r'<a href="%s">bar.txt</a>' % bar_url,
1592 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1594 self.failUnless(re.search(get_bar, res), res)
1595 for label in ['unlink', 'rename/relink']:
1596 for line in res.split("\n"):
1597 # find the line that contains the relevant button for bar.txt
1598 if ("form action" in line and
1599 ('value="%s"' % (label,)) in line and
1600 'value="bar.txt"' in line):
1601 # the form target should use a relative URL
1602 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1603 self.failUnlessIn('action="%s"' % foo_url, line)
1604 # and the when_done= should too
1605 #done_url = urllib.quote(???)
1606 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1608 # 'unlink' needs to use POST because it directly has a side effect
1609 if label == 'unlink':
1610 self.failUnlessIn('method="post"', line)
1613 self.fail("unable to find '%s bar.txt' line" % (label,))
1615 # the DIR reference just points to a URI
1616 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1617 get_sub = ((r'<td>DIR</td>')
1618 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1619 self.failUnless(re.search(get_sub, res), res)
1620 d.addCallback(_check)
1622 # look at a readonly directory
1623 d.addCallback(lambda res:
1624 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1626 self.failUnlessIn("(read-only)", res)
1627 self.failIfIn("Upload a file", res)
1628 d.addCallback(_check2)
1630 # and at a directory that contains a readonly directory
1631 d.addCallback(lambda res:
1632 self.GET(self.public_url, followRedirect=True))
1634 self.failUnless(re.search('<td>DIR-RO</td>'
1635 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1636 d.addCallback(_check3)
1638 # and an empty directory
1639 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1641 self.failUnlessIn("directory is empty", res)
1642 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" class="btn" value="Create" />', re.I)
1643 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1644 d.addCallback(_check4)
1646 # and at a literal directory
1647 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1648 d.addCallback(lambda res:
1649 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1651 self.failUnlessIn('(immutable)', res)
1652 self.failUnless(re.search('<td>FILE</td>'
1653 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1654 d.addCallback(_check5)
1657 def test_GET_DIRURL_badtype(self):
1658 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1662 self.public_url + "/foo?t=bogus")
1665 def test_GET_DIRURL_json(self):
1666 d = self.GET(self.public_url + "/foo?t=json")
1667 d.addCallback(self.failUnlessIsFooJSON)
1670 def test_GET_DIRURL_json_format(self):
1671 d = self.PUT(self.public_url + \
1672 "/foo/sdmf.txt?format=sdmf",
1673 self.NEWFILE_CONTENTS * 300000)
1674 d.addCallback(lambda ignored:
1675 self.PUT(self.public_url + \
1676 "/foo/mdmf.txt?format=mdmf",
1677 self.NEWFILE_CONTENTS * 300000))
1678 # Now we have an MDMF and SDMF file in the directory. If we GET
1679 # its JSON, we should see their encodings.
1680 d.addCallback(lambda ignored:
1681 self.GET(self.public_url + "/foo?t=json"))
1682 def _got_json(json):
1683 data = simplejson.loads(json)
1684 assert data[0] == "dirnode"
1687 kids = data['children']
1689 mdmf_data = kids['mdmf.txt'][1]
1690 self.failUnlessIn("format", mdmf_data)
1691 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1693 sdmf_data = kids['sdmf.txt'][1]
1694 self.failUnlessIn("format", sdmf_data)
1695 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1696 d.addCallback(_got_json)
1700 def test_POST_DIRURL_manifest_no_ophandle(self):
1701 d = self.shouldFail2(error.Error,
1702 "test_POST_DIRURL_manifest_no_ophandle",
1704 "slow operation requires ophandle=",
1705 self.POST, self.public_url, t="start-manifest")
1708 def test_POST_DIRURL_manifest(self):
1709 d = defer.succeed(None)
1710 def getman(ignored, output):
1711 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1712 followRedirect=True)
1713 d.addCallback(self.wait_for_operation, "125")
1714 d.addCallback(self.get_operation_results, "125", output)
1716 d.addCallback(getman, None)
1717 def _got_html(manifest):
1718 self.failUnlessIn("Manifest of SI=", manifest)
1719 self.failUnlessIn("<td>sub</td>", manifest)
1720 self.failUnlessIn(self._sub_uri, manifest)
1721 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1722 self.failUnlessIn(FAVICON_MARKUP, manifest)
1723 d.addCallback(_got_html)
1725 # both t=status and unadorned GET should be identical
1726 d.addCallback(lambda res: self.GET("/operations/125"))
1727 d.addCallback(_got_html)
1729 d.addCallback(getman, "html")
1730 d.addCallback(_got_html)
1731 d.addCallback(getman, "text")
1732 def _got_text(manifest):
1733 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1734 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1735 d.addCallback(_got_text)
1736 d.addCallback(getman, "JSON")
1738 data = res["manifest"]
1740 for (path_list, cap) in data:
1741 got[tuple(path_list)] = cap
1742 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1743 self.failUnlessIn((u"sub", u"baz.txt"), got)
1744 self.failUnlessIn("finished", res)
1745 self.failUnlessIn("origin", res)
1746 self.failUnlessIn("storage-index", res)
1747 self.failUnlessIn("verifycaps", res)
1748 self.failUnlessIn("stats", res)
1749 d.addCallback(_got_json)
1752 def test_POST_DIRURL_deepsize_no_ophandle(self):
1753 d = self.shouldFail2(error.Error,
1754 "test_POST_DIRURL_deepsize_no_ophandle",
1756 "slow operation requires ophandle=",
1757 self.POST, self.public_url, t="start-deep-size")
1760 def test_POST_DIRURL_deepsize(self):
1761 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1762 followRedirect=True)
1763 d.addCallback(self.wait_for_operation, "126")
1764 d.addCallback(self.get_operation_results, "126", "json")
1765 def _got_json(data):
1766 self.failUnlessReallyEqual(data["finished"], True)
1768 self.failUnless(size > 1000)
1769 d.addCallback(_got_json)
1770 d.addCallback(self.get_operation_results, "126", "text")
1772 mo = re.search(r'^size: (\d+)$', res, re.M)
1773 self.failUnless(mo, res)
1774 size = int(mo.group(1))
1775 # with directories, the size varies.
1776 self.failUnless(size > 1000)
1777 d.addCallback(_got_text)
1780 def test_POST_DIRURL_deepstats_no_ophandle(self):
1781 d = self.shouldFail2(error.Error,
1782 "test_POST_DIRURL_deepstats_no_ophandle",
1784 "slow operation requires ophandle=",
1785 self.POST, self.public_url, t="start-deep-stats")
1788 def test_POST_DIRURL_deepstats(self):
1789 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1790 followRedirect=True)
1791 d.addCallback(self.wait_for_operation, "127")
1792 d.addCallback(self.get_operation_results, "127", "json")
1793 def _got_json(stats):
1794 expected = {"count-immutable-files": 4,
1795 "count-mutable-files": 2,
1796 "count-literal-files": 0,
1798 "count-directories": 3,
1799 "size-immutable-files": 76,
1800 "size-literal-files": 0,
1801 #"size-directories": 1912, # varies
1802 #"largest-directory": 1590,
1803 "largest-directory-children": 8,
1804 "largest-immutable-file": 19,
1806 for k,v in expected.iteritems():
1807 self.failUnlessReallyEqual(stats[k], v,
1808 "stats[%s] was %s, not %s" %
1810 self.failUnlessReallyEqual(stats["size-files-histogram"],
1812 d.addCallback(_got_json)
1815 def test_POST_DIRURL_stream_manifest(self):
1816 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1818 self.failUnless(res.endswith("\n"))
1819 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1820 self.failUnlessReallyEqual(len(units), 10)
1821 self.failUnlessEqual(units[-1]["type"], "stats")
1823 self.failUnlessEqual(first["path"], [])
1824 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1825 self.failUnlessEqual(first["type"], "directory")
1826 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1827 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1828 self.failIfEqual(baz["storage-index"], None)
1829 self.failIfEqual(baz["verifycap"], None)
1830 self.failIfEqual(baz["repaircap"], None)
1831 # XXX: Add quux and baz to this test.
1833 d.addCallback(_check)
1836 def test_GET_DIRURL_uri(self):
1837 d = self.GET(self.public_url + "/foo?t=uri")
1839 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1840 d.addCallback(_check)
1843 def test_GET_DIRURL_readonly_uri(self):
1844 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1846 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1847 d.addCallback(_check)
1850 def test_PUT_NEWDIRURL(self):
1851 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1852 d.addCallback(lambda res:
1853 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1854 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1855 d.addCallback(self.failUnlessNodeKeysAre, [])
1858 def test_PUT_NEWDIRURL_mdmf(self):
1859 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1860 d.addCallback(lambda res:
1861 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1862 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1863 d.addCallback(lambda node:
1864 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1867 def test_PUT_NEWDIRURL_sdmf(self):
1868 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1870 d.addCallback(lambda res:
1871 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1872 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1873 d.addCallback(lambda node:
1874 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1877 def test_PUT_NEWDIRURL_bad_format(self):
1878 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1879 400, "Bad Request", "Unknown format: foo",
1880 self.PUT, self.public_url +
1881 "/foo/newdir=?t=mkdir&format=foo", "")
1883 def test_POST_NEWDIRURL(self):
1884 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1885 d.addCallback(lambda res:
1886 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1887 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1888 d.addCallback(self.failUnlessNodeKeysAre, [])
1891 def test_POST_NEWDIRURL_mdmf(self):
1892 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1893 d.addCallback(lambda res:
1894 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1895 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1896 d.addCallback(lambda node:
1897 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1900 def test_POST_NEWDIRURL_sdmf(self):
1901 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1902 d.addCallback(lambda res:
1903 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1904 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1905 d.addCallback(lambda node:
1906 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1909 def test_POST_NEWDIRURL_bad_format(self):
1910 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1911 400, "Bad Request", "Unknown format: foo",
1912 self.POST2, self.public_url + \
1913 "/foo/newdir?t=mkdir&format=foo", "")
1915 def test_POST_NEWDIRURL_emptyname(self):
1916 # an empty pathname component (i.e. a double-slash) is disallowed
1917 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1919 "The webapi does not allow empty pathname components, i.e. a double slash",
1920 self.POST, self.public_url + "//?t=mkdir")
1923 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1924 (newkids, caps) = self._create_initial_children()
1925 query = "/foo/newdir?t=mkdir-with-children"
1926 if version == MDMF_VERSION:
1927 query += "&format=mdmf"
1928 elif version == SDMF_VERSION:
1929 query += "&format=sdmf"
1931 version = SDMF_VERSION # for later
1932 d = self.POST2(self.public_url + query,
1933 simplejson.dumps(newkids))
1935 n = self.s.create_node_from_uri(uri.strip())
1936 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1937 self.failUnlessEqual(n._node.get_version(), version)
1938 d2.addCallback(lambda ign:
1939 self.failUnlessROChildURIIs(n, u"child-imm",
1941 d2.addCallback(lambda ign:
1942 self.failUnlessRWChildURIIs(n, u"child-mutable",
1944 d2.addCallback(lambda ign:
1945 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1947 d2.addCallback(lambda ign:
1948 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1949 caps['unknown_rocap']))
1950 d2.addCallback(lambda ign:
1951 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1952 caps['unknown_rwcap']))
1953 d2.addCallback(lambda ign:
1954 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1955 caps['unknown_immcap']))
1956 d2.addCallback(lambda ign:
1957 self.failUnlessRWChildURIIs(n, u"dirchild",
1959 d2.addCallback(lambda ign:
1960 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1962 d2.addCallback(lambda ign:
1963 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1964 caps['emptydircap']))
1966 d.addCallback(_check)
1967 d.addCallback(lambda res:
1968 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1969 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1970 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1971 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1972 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1975 def test_POST_NEWDIRURL_initial_children(self):
1976 return self._do_POST_NEWDIRURL_initial_children_test()
1978 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1979 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1981 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1982 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1984 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1985 (newkids, caps) = self._create_initial_children()
1986 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1987 400, "Bad Request", "Unknown format: foo",
1988 self.POST2, self.public_url + \
1989 "/foo/newdir?t=mkdir-with-children&format=foo",
1990 simplejson.dumps(newkids))
1992 def test_POST_NEWDIRURL_immutable(self):
1993 (newkids, caps) = self._create_immutable_children()
1994 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1995 simplejson.dumps(newkids))
1997 n = self.s.create_node_from_uri(uri.strip())
1998 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1999 d2.addCallback(lambda ign:
2000 self.failUnlessROChildURIIs(n, u"child-imm",
2002 d2.addCallback(lambda ign:
2003 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2004 caps['unknown_immcap']))
2005 d2.addCallback(lambda ign:
2006 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2008 d2.addCallback(lambda ign:
2009 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2011 d2.addCallback(lambda ign:
2012 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2013 caps['emptydircap']))
2015 d.addCallback(_check)
2016 d.addCallback(lambda res:
2017 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2018 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2019 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2020 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2021 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2022 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2023 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2024 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2025 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2026 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2027 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2028 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2029 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2030 d.addErrback(self.explain_web_error)
2033 def test_POST_NEWDIRURL_immutable_bad(self):
2034 (newkids, caps) = self._create_initial_children()
2035 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
2037 "needed to be immutable but was not",
2039 self.public_url + "/foo/newdir?t=mkdir-immutable",
2040 simplejson.dumps(newkids))
2043 def test_PUT_NEWDIRURL_exists(self):
2044 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
2045 d.addCallback(lambda res:
2046 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
2047 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2048 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2051 def test_PUT_NEWDIRURL_blocked(self):
2052 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
2053 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
2055 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
2056 d.addCallback(lambda res:
2057 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
2058 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2059 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2062 def test_PUT_NEWDIRURL_mkdirs(self):
2063 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
2064 d.addCallback(lambda res:
2065 self.failIfNodeHasChild(self._foo_node, u"newdir"))
2066 d.addCallback(lambda res:
2067 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
2068 d.addCallback(lambda res:
2069 self._foo_node.get_child_at_path(u"subdir/newdir"))
2070 d.addCallback(self.failUnlessNodeKeysAre, [])
2073 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
2074 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
2075 d.addCallback(lambda ignored:
2076 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
2077 d.addCallback(lambda ignored:
2078 self.failIfNodeHasChild(self._foo_node, u"newdir"))
2079 d.addCallback(lambda ignored:
2080 self._foo_node.get_child_at_path(u"subdir"))
2081 def _got_subdir(subdir):
2082 # XXX: What we want?
2083 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
2084 self.failUnlessNodeHasChild(subdir, u"newdir")
2085 return subdir.get_child_at_path(u"newdir")
2086 d.addCallback(_got_subdir)
2087 d.addCallback(lambda newdir:
2088 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
2091 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
2092 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
2093 d.addCallback(lambda ignored:
2094 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
2095 d.addCallback(lambda ignored:
2096 self.failIfNodeHasChild(self._foo_node, u"newdir"))
2097 d.addCallback(lambda ignored:
2098 self._foo_node.get_child_at_path(u"subdir"))
2099 def _got_subdir(subdir):
2100 # XXX: What we want?
2101 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
2102 self.failUnlessNodeHasChild(subdir, u"newdir")
2103 return subdir.get_child_at_path(u"newdir")
2104 d.addCallback(_got_subdir)
2105 d.addCallback(lambda newdir:
2106 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
2109 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
2110 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
2111 400, "Bad Request", "Unknown format: foo",
2112 self.PUT, self.public_url + \
2113 "/foo/subdir/newdir?t=mkdir&format=foo",
2116 def test_DELETE_DIRURL(self):
2117 d = self.DELETE(self.public_url + "/foo")
2118 d.addCallback(lambda res:
2119 self.failIfNodeHasChild(self.public_root, u"foo"))
2122 def test_DELETE_DIRURL_missing(self):
2123 d = self.DELETE(self.public_url + "/foo/missing")
2124 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
2125 d.addCallback(lambda res:
2126 self.failUnlessNodeHasChild(self.public_root, u"foo"))
2129 def test_DELETE_DIRURL_missing2(self):
2130 d = self.DELETE(self.public_url + "/missing")
2131 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
2134 def dump_root(self):
2136 w = webish.DirnodeWalkerMixin()
2137 def visitor(childpath, childnode, metadata):
2139 d = w.walk(self.public_root, visitor)
2142 def failUnlessNodeKeysAre(self, node, expected_keys):
2143 for k in expected_keys:
2144 assert isinstance(k, unicode)
2146 def _check(children):
2147 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
2148 d.addCallback(_check)
2150 def failUnlessNodeHasChild(self, node, name):
2151 assert isinstance(name, unicode)
2153 def _check(children):
2154 self.failUnlessIn(name, children)
2155 d.addCallback(_check)
2157 def failIfNodeHasChild(self, node, name):
2158 assert isinstance(name, unicode)
2160 def _check(children):
2161 self.failIfIn(name, children)
2162 d.addCallback(_check)
2165 def failUnlessChildContentsAre(self, node, name, expected_contents):
2166 assert isinstance(name, unicode)
2167 d = node.get_child_at_path(name)
2168 d.addCallback(lambda node: download_to_data(node))
2169 def _check(contents):
2170 self.failUnlessReallyEqual(contents, expected_contents)
2171 d.addCallback(_check)
2174 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
2175 assert isinstance(name, unicode)
2176 d = node.get_child_at_path(name)
2177 d.addCallback(lambda node: node.download_best_version())
2178 def _check(contents):
2179 self.failUnlessReallyEqual(contents, expected_contents)
2180 d.addCallback(_check)
2183 def failUnlessRWChildURIIs(self, node, name, expected_uri):
2184 assert isinstance(name, unicode)
2185 d = node.get_child_at_path(name)
2187 self.failUnless(child.is_unknown() or not child.is_readonly())
2188 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2189 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
2190 expected_ro_uri = self._make_readonly(expected_uri)
2192 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2193 d.addCallback(_check)
2196 def failUnlessROChildURIIs(self, node, name, expected_uri):
2197 assert isinstance(name, unicode)
2198 d = node.get_child_at_path(name)
2200 self.failUnless(child.is_unknown() or child.is_readonly())
2201 self.failUnlessReallyEqual(child.get_write_uri(), None)
2202 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2203 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
2204 d.addCallback(_check)
2207 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
2208 assert isinstance(name, unicode)
2209 d = node.get_child_at_path(name)
2211 self.failUnless(child.is_unknown() or not child.is_readonly())
2212 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
2213 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
2214 expected_ro_uri = self._make_readonly(got_uri)
2216 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2217 d.addCallback(_check)
2220 def failUnlessURIMatchesROChild(self, got_uri, node, name):
2221 assert isinstance(name, unicode)
2222 d = node.get_child_at_path(name)
2224 self.failUnless(child.is_unknown() or child.is_readonly())
2225 self.failUnlessReallyEqual(child.get_write_uri(), None)
2226 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
2227 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
2228 d.addCallback(_check)
2231 def failUnlessCHKURIHasContents(self, got_uri, contents):
2232 self.failUnless(self.get_all_contents()[got_uri] == contents)
2234 def test_POST_upload(self):
2235 d = self.POST(self.public_url + "/foo", t="upload",
2236 file=("new.txt", self.NEWFILE_CONTENTS))
2238 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2239 d.addCallback(lambda res:
2240 self.failUnlessChildContentsAre(fn, u"new.txt",
2241 self.NEWFILE_CONTENTS))
2244 def test_POST_upload_unicode(self):
2245 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2246 d = self.POST(self.public_url + "/foo", t="upload",
2247 file=(filename, self.NEWFILE_CONTENTS))
2249 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2250 d.addCallback(lambda res:
2251 self.failUnlessChildContentsAre(fn, filename,
2252 self.NEWFILE_CONTENTS))
2253 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2254 d.addCallback(lambda res: self.GET(target_url))
2255 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2256 self.NEWFILE_CONTENTS,
2260 def test_POST_upload_unicode_named(self):
2261 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2262 d = self.POST(self.public_url + "/foo", t="upload",
2264 file=("overridden", self.NEWFILE_CONTENTS))
2266 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2267 d.addCallback(lambda res:
2268 self.failUnlessChildContentsAre(fn, filename,
2269 self.NEWFILE_CONTENTS))
2270 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2271 d.addCallback(lambda res: self.GET(target_url))
2272 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2273 self.NEWFILE_CONTENTS,
2277 def test_POST_upload_no_link(self):
2278 d = self.POST("/uri", t="upload",
2279 file=("new.txt", self.NEWFILE_CONTENTS))
2280 def _check_upload_results(page):
2281 # this should be a page which describes the results of the upload
2282 # that just finished.
2283 self.failUnlessIn("Upload Results:", page)
2284 self.failUnlessIn("URI:", page)
2285 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2286 mo = uri_re.search(page)
2287 self.failUnless(mo, page)
2288 new_uri = mo.group(1)
2290 d.addCallback(_check_upload_results)
2291 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2294 def test_POST_upload_no_link_whendone(self):
2295 d = self.POST("/uri", t="upload", when_done="/",
2296 file=("new.txt", self.NEWFILE_CONTENTS))
2297 d.addBoth(self.shouldRedirect, "/")
2300 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2301 d = defer.maybeDeferred(callable, *args, **kwargs)
2303 if isinstance(res, failure.Failure):
2304 res.trap(error.PageRedirect)
2305 statuscode = res.value.status
2306 target = res.value.location
2307 return checker(statuscode, target)
2308 self.fail("%s: callable was supposed to redirect, not return '%s'"
2313 def test_POST_upload_no_link_whendone_results(self):
2314 def check(statuscode, target):
2315 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2316 self.failUnless(target.startswith(self.webish_url), target)
2317 return client.getPage(target, method="GET")
2318 # We encode "uri" as "%75ri" to exercise a case affected by ticket #1860.
2319 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2321 self.POST, "/uri", t="upload",
2322 when_done="/%75ri/%(uri)s",
2323 file=("new.txt", self.NEWFILE_CONTENTS))
2324 d.addCallback(lambda res:
2325 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2328 def test_POST_upload_no_link_mutable(self):
2329 d = self.POST("/uri", t="upload", mutable="true",
2330 file=("new.txt", self.NEWFILE_CONTENTS))
2331 def _check(filecap):
2332 filecap = filecap.strip()
2333 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2334 self.filecap = filecap
2335 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2336 self.failUnlessIn(u.get_storage_index(), self.get_all_contents())
2337 n = self.s.create_node_from_uri(filecap)
2338 return n.download_best_version()
2339 d.addCallback(_check)
2341 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2342 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2343 d.addCallback(_check2)
2345 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2346 return self.GET("/file/%s" % urllib.quote(self.filecap))
2347 d.addCallback(_check3)
2349 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2350 d.addCallback(_check4)
2353 def test_POST_upload_no_link_mutable_toobig(self):
2354 # The SDMF size limit is no longer in place, so we should be
2355 # able to upload mutable files that are as large as we want them
2357 d = self.POST("/uri", t="upload", mutable="true",
2358 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2362 def test_POST_upload_format_unlinked(self):
2363 def _check_upload_unlinked(ign, format, uri_prefix):
2364 filename = format + ".txt"
2365 d = self.POST("/uri?t=upload&format=" + format,
2366 file=(filename, self.NEWFILE_CONTENTS * 300000))
2367 def _got_results(results):
2368 if format.upper() in ("SDMF", "MDMF"):
2369 # webapi.rst says this returns a filecap
2372 # for immutable, it returns an "upload results page", and
2373 # the filecap is buried inside
2374 line = [l for l in results.split("\n") if "URI: " in l][0]
2375 mo = re.search(r'<span>([^<]+)</span>', line)
2376 filecap = mo.group(1)
2377 self.failUnless(filecap.startswith(uri_prefix),
2378 (uri_prefix, filecap))
2379 return self.GET("/uri/%s?t=json" % filecap)
2380 d.addCallback(_got_results)
2381 def _got_json(json):
2382 data = simplejson.loads(json)
2384 self.failUnlessIn("format", data)
2385 self.failUnlessEqual(data["format"], format.upper())
2386 d.addCallback(_got_json)
2388 d = defer.succeed(None)
2389 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2390 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2391 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2392 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2395 def test_POST_upload_bad_format_unlinked(self):
2396 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2397 400, "Bad Request", "Unknown format: foo",
2399 "/uri?t=upload&format=foo",
2400 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2402 def test_POST_upload_format(self):
2403 def _check_upload(ign, format, uri_prefix, fn=None):
2404 filename = format + ".txt"
2405 d = self.POST(self.public_url +
2406 "/foo?t=upload&format=" + format,
2407 file=(filename, self.NEWFILE_CONTENTS * 300000))
2408 def _got_filecap(filecap):
2410 filenameu = unicode(filename)
2411 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2412 self.failUnless(filecap.startswith(uri_prefix))
2413 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2414 d.addCallback(_got_filecap)
2415 def _got_json(json):
2416 data = simplejson.loads(json)
2418 self.failUnlessIn("format", data)
2419 self.failUnlessEqual(data["format"], format.upper())
2420 d.addCallback(_got_json)
2423 d = defer.succeed(None)
2424 d.addCallback(_check_upload, "chk", "URI:CHK")
2425 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2426 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2427 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2430 def test_POST_upload_bad_format(self):
2431 return self.shouldHTTPError("POST_upload_bad_format",
2432 400, "Bad Request", "Unknown format: foo",
2433 self.POST, self.public_url + \
2434 "/foo?t=upload&format=foo",
2435 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2437 def test_POST_upload_mutable(self):
2438 # this creates a mutable file
2439 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2440 file=("new.txt", self.NEWFILE_CONTENTS))
2442 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2443 d.addCallback(lambda res:
2444 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2445 self.NEWFILE_CONTENTS))
2446 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2448 self.failUnless(IMutableFileNode.providedBy(newnode))
2449 self.failUnless(newnode.is_mutable())
2450 self.failIf(newnode.is_readonly())
2451 self._mutable_node = newnode
2452 self._mutable_uri = newnode.get_uri()
2455 # now upload it again and make sure that the URI doesn't change
2456 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2457 d.addCallback(lambda res:
2458 self.POST(self.public_url + "/foo", t="upload",
2460 file=("new.txt", NEWER_CONTENTS)))
2461 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2462 d.addCallback(lambda res:
2463 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2465 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2467 self.failUnless(IMutableFileNode.providedBy(newnode))
2468 self.failUnless(newnode.is_mutable())
2469 self.failIf(newnode.is_readonly())
2470 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2471 d.addCallback(_got2)
2473 # upload a second time, using PUT instead of POST
2474 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2475 d.addCallback(lambda res:
2476 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2477 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2478 d.addCallback(lambda res:
2479 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2482 # finally list the directory, since mutable files are displayed
2483 # slightly differently
2485 d.addCallback(lambda res:
2486 self.GET(self.public_url + "/foo/",
2487 followRedirect=True))
2488 def _check_page(res):
2489 # TODO: assert more about the contents
2490 self.failUnlessIn("SSK", res)
2492 d.addCallback(_check_page)
2494 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2496 self.failUnless(IMutableFileNode.providedBy(newnode))
2497 self.failUnless(newnode.is_mutable())
2498 self.failIf(newnode.is_readonly())
2499 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2500 d.addCallback(_got3)
2502 # look at the JSON form of the enclosing directory
2503 d.addCallback(lambda res:
2504 self.GET(self.public_url + "/foo/?t=json",
2505 followRedirect=True))
2506 def _check_page_json(res):
2507 parsed = simplejson.loads(res)
2508 self.failUnlessEqual(parsed[0], "dirnode")
2509 children = dict( [(unicode(name),value)
2511 in parsed[1]["children"].iteritems()] )
2512 self.failUnlessIn(u"new.txt", children)
2513 new_json = children[u"new.txt"]
2514 self.failUnlessEqual(new_json[0], "filenode")
2515 self.failUnless(new_json[1]["mutable"])
2516 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2517 ro_uri = self._mutable_node.get_readonly().to_string()
2518 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2519 d.addCallback(_check_page_json)
2521 # and the JSON form of the file
2522 d.addCallback(lambda res:
2523 self.GET(self.public_url + "/foo/new.txt?t=json"))
2524 def _check_file_json(res):
2525 parsed = simplejson.loads(res)
2526 self.failUnlessEqual(parsed[0], "filenode")
2527 self.failUnless(parsed[1]["mutable"])
2528 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2529 ro_uri = self._mutable_node.get_readonly().to_string()
2530 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2531 d.addCallback(_check_file_json)
2533 # and look at t=uri and t=readonly-uri
2534 d.addCallback(lambda res:
2535 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2536 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2537 d.addCallback(lambda res:
2538 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2539 def _check_ro_uri(res):
2540 ro_uri = self._mutable_node.get_readonly().to_string()
2541 self.failUnlessReallyEqual(res, ro_uri)
2542 d.addCallback(_check_ro_uri)
2544 # make sure we can get to it from /uri/URI
2545 d.addCallback(lambda res:
2546 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2547 d.addCallback(lambda res:
2548 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2550 # and that HEAD computes the size correctly
2551 d.addCallback(lambda res:
2552 self.HEAD(self.public_url + "/foo/new.txt",
2553 return_response=True))
2554 def _got_headers((res, status, headers)):
2555 self.failUnlessReallyEqual(res, "")
2556 self.failUnlessReallyEqual(headers["content-length"][0],
2557 str(len(NEW2_CONTENTS)))
2558 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2559 d.addCallback(_got_headers)
2561 # make sure that outdated size limits aren't enforced anymore.
2562 d.addCallback(lambda ignored:
2563 self.POST(self.public_url + "/foo", t="upload",
2566 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2567 d.addErrback(self.dump_error)
2570 def test_POST_upload_mutable_toobig(self):
2571 # SDMF had a size limti that was removed a while ago. MDMF has
2572 # never had a size limit. Test to make sure that we do not
2573 # encounter errors when trying to upload large mutable files,
2574 # since there should be no coded prohibitions regarding large
2576 d = self.POST(self.public_url + "/foo",
2577 t="upload", mutable="true",
2578 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2581 def dump_error(self, f):
2582 # if the web server returns an error code (like 400 Bad Request),
2583 # web.client.getPage puts the HTTP response body into the .response
2584 # attribute of the exception object that it gives back. It does not
2585 # appear in the Failure's repr(), so the ERROR that trial displays
2586 # will be rather terse and unhelpful. addErrback this method to the
2587 # end of your chain to get more information out of these errors.
2588 if f.check(error.Error):
2589 print "web.error.Error:"
2591 print f.value.response
2594 def test_POST_upload_replace(self):
2595 d = self.POST(self.public_url + "/foo", t="upload",
2596 file=("bar.txt", self.NEWFILE_CONTENTS))
2598 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2599 d.addCallback(lambda res:
2600 self.failUnlessChildContentsAre(fn, u"bar.txt",
2601 self.NEWFILE_CONTENTS))
2604 def test_POST_upload_no_replace_ok(self):
2605 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2606 file=("new.txt", self.NEWFILE_CONTENTS))
2607 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2608 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2609 self.NEWFILE_CONTENTS))
2612 def test_POST_upload_no_replace_queryarg(self):
2613 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2614 file=("bar.txt", self.NEWFILE_CONTENTS))
2615 d.addBoth(self.shouldFail, error.Error,
2616 "POST_upload_no_replace_queryarg",
2618 "There was already a child by that name, and you asked me "
2619 "to not replace it")
2620 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2621 d.addCallback(self.failUnlessIsBarDotTxt)
2624 def test_POST_upload_no_replace_field(self):
2625 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2626 file=("bar.txt", self.NEWFILE_CONTENTS))
2627 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2629 "There was already a child by that name, and you asked me "
2630 "to not replace it")
2631 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2632 d.addCallback(self.failUnlessIsBarDotTxt)
2635 def test_POST_upload_whendone(self):
2636 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2637 file=("new.txt", self.NEWFILE_CONTENTS))
2638 d.addBoth(self.shouldRedirect, "/THERE")
2640 d.addCallback(lambda res:
2641 self.failUnlessChildContentsAre(fn, u"new.txt",
2642 self.NEWFILE_CONTENTS))
2645 def test_POST_upload_named(self):
2647 d = self.POST(self.public_url + "/foo", t="upload",
2648 name="new.txt", file=self.NEWFILE_CONTENTS)
2649 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2650 d.addCallback(lambda res:
2651 self.failUnlessChildContentsAre(fn, u"new.txt",
2652 self.NEWFILE_CONTENTS))
2655 def test_POST_upload_named_badfilename(self):
2656 d = self.POST(self.public_url + "/foo", t="upload",
2657 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2658 d.addBoth(self.shouldFail, error.Error,
2659 "test_POST_upload_named_badfilename",
2661 "name= may not contain a slash",
2663 # make sure that nothing was added
2664 d.addCallback(lambda res:
2665 self.failUnlessNodeKeysAre(self._foo_node,
2666 [self._htmlname_unicode,
2667 u"bar.txt", u"baz.txt", u"blockingfile",
2668 u"empty", u"n\u00fc.txt", u"quux.txt",
2672 def test_POST_FILEURL_check(self):
2673 bar_url = self.public_url + "/foo/bar.txt"
2674 d = self.POST(bar_url, t="check")
2676 self.failUnlessIn("Healthy :", res)
2677 d.addCallback(_check)
2678 redir_url = "http://allmydata.org/TARGET"
2679 def _check2(statuscode, target):
2680 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2681 self.failUnlessReallyEqual(target, redir_url)
2682 d.addCallback(lambda res:
2683 self.shouldRedirect2("test_POST_FILEURL_check",
2687 when_done=redir_url))
2688 d.addCallback(lambda res:
2689 self.POST(bar_url, t="check", return_to=redir_url))
2691 self.failUnlessIn("Healthy :", res)
2692 self.failUnlessIn("Return to file", res)
2693 self.failUnlessIn(redir_url, res)
2694 d.addCallback(_check3)
2696 d.addCallback(lambda res:
2697 self.POST(bar_url, t="check", output="JSON"))
2698 def _check_json(res):
2699 data = simplejson.loads(res)
2700 self.failUnlessIn("storage-index", data)
2701 self.failUnless(data["results"]["healthy"])
2702 d.addCallback(_check_json)
2706 def test_POST_FILEURL_check_and_repair(self):
2707 bar_url = self.public_url + "/foo/bar.txt"
2708 d = self.POST(bar_url, t="check", repair="true")
2710 self.failUnlessIn("Healthy :", res)
2711 d.addCallback(_check)
2712 redir_url = "http://allmydata.org/TARGET"
2713 def _check2(statuscode, target):
2714 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2715 self.failUnlessReallyEqual(target, redir_url)
2716 d.addCallback(lambda res:
2717 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2720 t="check", repair="true",
2721 when_done=redir_url))
2722 d.addCallback(lambda res:
2723 self.POST(bar_url, t="check", return_to=redir_url))
2725 self.failUnlessIn("Healthy :", res)
2726 self.failUnlessIn("Return to file", res)
2727 self.failUnlessIn(redir_url, res)
2728 d.addCallback(_check3)
2731 def test_POST_DIRURL_check(self):
2732 foo_url = self.public_url + "/foo/"
2733 d = self.POST(foo_url, t="check")
2735 self.failUnlessIn("Healthy :", res)
2736 d.addCallback(_check)
2737 redir_url = "http://allmydata.org/TARGET"
2738 def _check2(statuscode, target):
2739 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2740 self.failUnlessReallyEqual(target, redir_url)
2741 d.addCallback(lambda res:
2742 self.shouldRedirect2("test_POST_DIRURL_check",
2746 when_done=redir_url))
2747 d.addCallback(lambda res:
2748 self.POST(foo_url, t="check", return_to=redir_url))
2750 self.failUnlessIn("Healthy :", res)
2751 self.failUnlessIn("Return to file/directory", res)
2752 self.failUnlessIn(redir_url, res)
2753 d.addCallback(_check3)
2755 d.addCallback(lambda res:
2756 self.POST(foo_url, t="check", output="JSON"))
2757 def _check_json(res):
2758 data = simplejson.loads(res)
2759 self.failUnlessIn("storage-index", data)
2760 self.failUnless(data["results"]["healthy"])
2761 d.addCallback(_check_json)
2765 def test_POST_DIRURL_check_and_repair(self):
2766 foo_url = self.public_url + "/foo/"
2767 d = self.POST(foo_url, t="check", repair="true")
2769 self.failUnlessIn("Healthy :", res)
2770 d.addCallback(_check)
2771 redir_url = "http://allmydata.org/TARGET"
2772 def _check2(statuscode, target):
2773 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2774 self.failUnlessReallyEqual(target, redir_url)
2775 d.addCallback(lambda res:
2776 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2779 t="check", repair="true",
2780 when_done=redir_url))
2781 d.addCallback(lambda res:
2782 self.POST(foo_url, t="check", return_to=redir_url))
2784 self.failUnlessIn("Healthy :", res)
2785 self.failUnlessIn("Return to file/directory", res)
2786 self.failUnlessIn(redir_url, res)
2787 d.addCallback(_check3)
2790 def test_POST_FILEURL_mdmf_check(self):
2791 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2792 d = self.POST(quux_url, t="check")
2794 self.failUnlessIn("Healthy", res)
2795 d.addCallback(_check)
2796 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2797 d.addCallback(lambda ignored:
2798 self.POST(quux_extension_url, t="check"))
2799 d.addCallback(_check)
2802 def test_POST_FILEURL_mdmf_check_and_repair(self):
2803 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2804 d = self.POST(quux_url, t="check", repair="true")
2806 self.failUnlessIn("Healthy", res)
2807 d.addCallback(_check)
2808 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2809 d.addCallback(lambda ignored:
2810 self.POST(quux_extension_url, t="check", repair="true"))
2811 d.addCallback(_check)
2814 def wait_for_operation(self, ignored, ophandle):
2815 url = "/operations/" + ophandle
2816 url += "?t=status&output=JSON"
2819 data = simplejson.loads(res)
2820 if not data["finished"]:
2821 d = self.stall(delay=1.0)
2822 d.addCallback(self.wait_for_operation, ophandle)
2828 def get_operation_results(self, ignored, ophandle, output=None):
2829 url = "/operations/" + ophandle
2832 url += "&output=" + output
2835 if output and output.lower() == "json":
2836 return simplejson.loads(res)
2841 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2842 d = self.shouldFail2(error.Error,
2843 "test_POST_DIRURL_deepcheck_no_ophandle",
2845 "slow operation requires ophandle=",
2846 self.POST, self.public_url, t="start-deep-check")
2849 def test_POST_DIRURL_deepcheck(self):
2850 def _check_redirect(statuscode, target):
2851 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2852 self.failUnless(target.endswith("/operations/123"))
2853 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2854 self.POST, self.public_url,
2855 t="start-deep-check", ophandle="123")
2856 d.addCallback(self.wait_for_operation, "123")
2857 def _check_json(data):
2858 self.failUnlessReallyEqual(data["finished"], True)
2859 self.failUnlessReallyEqual(data["count-objects-checked"], 11)
2860 self.failUnlessReallyEqual(data["count-objects-healthy"], 11)
2861 d.addCallback(_check_json)
2862 d.addCallback(self.get_operation_results, "123", "html")
2863 def _check_html(res):
2864 self.failUnlessIn("Objects Checked: <span>11</span>", res)
2865 self.failUnlessIn("Objects Healthy: <span>11</span>", res)
2866 self.failUnlessIn(FAVICON_MARKUP, res)
2867 d.addCallback(_check_html)
2869 d.addCallback(lambda res:
2870 self.GET("/operations/123/"))
2871 d.addCallback(_check_html) # should be the same as without the slash
2873 d.addCallback(lambda res:
2874 self.shouldFail2(error.Error, "one", "404 Not Found",
2875 "No detailed results for SI bogus",
2876 self.GET, "/operations/123/bogus"))
2878 foo_si = self._foo_node.get_storage_index()
2879 foo_si_s = base32.b2a(foo_si)
2880 d.addCallback(lambda res:
2881 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2882 def _check_foo_json(res):
2883 data = simplejson.loads(res)
2884 self.failUnlessEqual(data["storage-index"], foo_si_s)
2885 self.failUnless(data["results"]["healthy"])
2886 d.addCallback(_check_foo_json)
2889 def test_POST_DIRURL_deepcheck_and_repair(self):
2890 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2891 ophandle="124", output="json", followRedirect=True)
2892 d.addCallback(self.wait_for_operation, "124")
2893 def _check_json(data):
2894 self.failUnlessReallyEqual(data["finished"], True)
2895 self.failUnlessReallyEqual(data["count-objects-checked"], 11)
2896 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 11)
2897 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2898 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2899 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2900 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2901 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2902 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 11)
2903 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2904 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2905 d.addCallback(_check_json)
2906 d.addCallback(self.get_operation_results, "124", "html")
2907 def _check_html(res):
2908 self.failUnlessIn("Objects Checked: <span>11</span>", res)
2910 self.failUnlessIn("Objects Healthy (before repair): <span>11</span>", res)
2911 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2912 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2914 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2915 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2916 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2918 self.failUnlessIn("Objects Healthy (after repair): <span>11</span>", res)
2919 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2920 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2922 self.failUnlessIn(FAVICON_MARKUP, res)
2923 d.addCallback(_check_html)
2926 def test_POST_FILEURL_bad_t(self):
2927 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2928 "POST to file: bad t=bogus",
2929 self.POST, self.public_url + "/foo/bar.txt",
2933 def test_POST_mkdir(self): # return value?
2934 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2935 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2936 d.addCallback(self.failUnlessNodeKeysAre, [])
2939 def test_POST_mkdir_mdmf(self):
2940 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2941 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2942 d.addCallback(lambda node:
2943 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2946 def test_POST_mkdir_sdmf(self):
2947 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2948 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2949 d.addCallback(lambda node:
2950 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2953 def test_POST_mkdir_bad_format(self):
2954 return self.shouldHTTPError("POST_mkdir_bad_format",
2955 400, "Bad Request", "Unknown format: foo",
2956 self.POST, self.public_url +
2957 "/foo?t=mkdir&name=newdir&format=foo")
2959 def test_POST_mkdir_initial_children(self):
2960 (newkids, caps) = self._create_initial_children()
2961 d = self.POST2(self.public_url +
2962 "/foo?t=mkdir-with-children&name=newdir",
2963 simplejson.dumps(newkids))
2964 d.addCallback(lambda res:
2965 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2966 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2967 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2968 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2969 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2972 def test_POST_mkdir_initial_children_mdmf(self):
2973 (newkids, caps) = self._create_initial_children()
2974 d = self.POST2(self.public_url +
2975 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2976 simplejson.dumps(newkids))
2977 d.addCallback(lambda res:
2978 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2979 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2980 d.addCallback(lambda node:
2981 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2982 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2983 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2988 def test_POST_mkdir_initial_children_sdmf(self):
2989 (newkids, caps) = self._create_initial_children()
2990 d = self.POST2(self.public_url +
2991 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2992 simplejson.dumps(newkids))
2993 d.addCallback(lambda res:
2994 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2995 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2996 d.addCallback(lambda node:
2997 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2998 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2999 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
3003 def test_POST_mkdir_initial_children_bad_format(self):
3004 (newkids, caps) = self._create_initial_children()
3005 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
3006 400, "Bad Request", "Unknown format: foo",
3007 self.POST, self.public_url + \
3008 "/foo?t=mkdir-with-children&name=newdir&format=foo",
3009 simplejson.dumps(newkids))
3011 def test_POST_mkdir_immutable(self):
3012 (newkids, caps) = self._create_immutable_children()
3013 d = self.POST2(self.public_url +
3014 "/foo?t=mkdir-immutable&name=newdir",
3015 simplejson.dumps(newkids))
3016 d.addCallback(lambda res:
3017 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
3018 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3019 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
3020 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3021 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
3022 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3023 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
3024 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3025 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
3026 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3027 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
3028 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3029 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
3032 def test_POST_mkdir_immutable_bad(self):
3033 (newkids, caps) = self._create_initial_children()
3034 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
3036 "needed to be immutable but was not",
3039 "/foo?t=mkdir-immutable&name=newdir",
3040 simplejson.dumps(newkids))
3043 def test_POST_mkdir_2(self):
3044 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
3045 d.addCallback(lambda res:
3046 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
3047 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3048 d.addCallback(self.failUnlessNodeKeysAre, [])
3051 def test_POST_mkdirs_2(self):
3052 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
3053 d.addCallback(lambda res:
3054 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
3055 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
3056 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
3057 d.addCallback(self.failUnlessNodeKeysAre, [])
3060 def test_POST_mkdir_no_parentdir_noredirect(self):
3061 d = self.POST("/uri?t=mkdir")
3062 def _after_mkdir(res):
3063 uri.DirectoryURI.init_from_string(res)
3064 d.addCallback(_after_mkdir)
3067 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
3068 d = self.POST("/uri?t=mkdir&format=mdmf")
3069 def _after_mkdir(res):
3070 u = uri.from_string(res)
3071 # Check that this is an MDMF writecap
3072 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3073 d.addCallback(_after_mkdir)
3076 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
3077 d = self.POST("/uri?t=mkdir&format=sdmf")
3078 def _after_mkdir(res):
3079 u = uri.from_string(res)
3080 self.failUnlessIsInstance(u, uri.DirectoryURI)
3081 d.addCallback(_after_mkdir)
3084 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
3085 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
3086 400, "Bad Request", "Unknown format: foo",
3087 self.POST, self.public_url +
3088 "/uri?t=mkdir&format=foo")
3090 def test_POST_mkdir_no_parentdir_noredirect2(self):
3091 # make sure form-based arguments (as on the welcome page) still work
3092 d = self.POST("/uri", t="mkdir")
3093 def _after_mkdir(res):
3094 uri.DirectoryURI.init_from_string(res)
3095 d.addCallback(_after_mkdir)
3096 d.addErrback(self.explain_web_error)
3099 def test_POST_mkdir_no_parentdir_redirect(self):
3100 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
3101 d.addBoth(self.shouldRedirect, None, statuscode='303')
3102 def _check_target(target):
3103 target = urllib.unquote(target)
3104 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
3105 d.addCallback(_check_target)
3108 def test_POST_mkdir_no_parentdir_redirect2(self):
3109 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
3110 d.addBoth(self.shouldRedirect, None, statuscode='303')
3111 def _check_target(target):
3112 target = urllib.unquote(target)
3113 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
3114 d.addCallback(_check_target)
3115 d.addErrback(self.explain_web_error)
3118 def _make_readonly(self, u):
3119 ro_uri = uri.from_string(u).get_readonly()
3122 return ro_uri.to_string()
3124 def _create_initial_children(self):
3125 contents, n, filecap1 = self.makefile(12)
3126 md1 = {"metakey1": "metavalue1"}
3127 filecap2 = make_mutable_file_uri()
3128 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
3129 filecap3 = node3.get_readonly_uri()
3130 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
3131 dircap = DirectoryNode(node4, None, None).get_uri()
3132 mdmfcap = make_mutable_file_uri(mdmf=True)
3133 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3134 emptydircap = "URI:DIR2-LIT:"
3135 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
3136 "ro_uri": self._make_readonly(filecap1),
3137 "metadata": md1, }],
3138 u"child-mutable": ["filenode", {"rw_uri": filecap2,
3139 "ro_uri": self._make_readonly(filecap2)}],
3140 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
3141 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
3142 "ro_uri": unknown_rocap}],
3143 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
3144 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3145 u"dirchild": ["dirnode", {"rw_uri": dircap,
3146 "ro_uri": self._make_readonly(dircap)}],
3147 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3148 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3149 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
3150 "ro_uri": self._make_readonly(mdmfcap)}],
3152 return newkids, {'filecap1': filecap1,
3153 'filecap2': filecap2,
3154 'filecap3': filecap3,
3155 'unknown_rwcap': unknown_rwcap,
3156 'unknown_rocap': unknown_rocap,
3157 'unknown_immcap': unknown_immcap,
3159 'litdircap': litdircap,
3160 'emptydircap': emptydircap,
3163 def _create_immutable_children(self):
3164 contents, n, filecap1 = self.makefile(12)
3165 md1 = {"metakey1": "metavalue1"}
3166 tnode = create_chk_filenode("immutable directory contents\n"*10,
3167 self.get_all_contents())
3168 dnode = DirectoryNode(tnode, None, None)
3169 assert not dnode.is_mutable()
3170 immdircap = dnode.get_uri()
3171 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3172 emptydircap = "URI:DIR2-LIT:"
3173 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
3174 "metadata": md1, }],
3175 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3176 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
3177 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3178 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3180 return newkids, {'filecap1': filecap1,
3181 'unknown_immcap': unknown_immcap,
3182 'immdircap': immdircap,
3183 'litdircap': litdircap,
3184 'emptydircap': emptydircap}
3186 def test_POST_mkdir_no_parentdir_initial_children(self):
3187 (newkids, caps) = self._create_initial_children()
3188 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
3189 def _after_mkdir(res):
3190 self.failUnless(res.startswith("URI:DIR"), res)
3191 n = self.s.create_node_from_uri(res)
3192 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3193 d2.addCallback(lambda ign:
3194 self.failUnlessROChildURIIs(n, u"child-imm",
3196 d2.addCallback(lambda ign:
3197 self.failUnlessRWChildURIIs(n, u"child-mutable",
3199 d2.addCallback(lambda ign:
3200 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
3202 d2.addCallback(lambda ign:
3203 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
3204 caps['unknown_rwcap']))
3205 d2.addCallback(lambda ign:
3206 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
3207 caps['unknown_rocap']))
3208 d2.addCallback(lambda ign:
3209 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3210 caps['unknown_immcap']))
3211 d2.addCallback(lambda ign:
3212 self.failUnlessRWChildURIIs(n, u"dirchild",
3215 d.addCallback(_after_mkdir)
3218 def test_POST_mkdir_no_parentdir_unexpected_children(self):
3219 # the regular /uri?t=mkdir operation is specified to ignore its body.
3220 # Only t=mkdir-with-children pays attention to it.
3221 (newkids, caps) = self._create_initial_children()
3222 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
3224 "t=mkdir does not accept children=, "
3225 "try t=mkdir-with-children instead",
3226 self.POST2, "/uri?t=mkdir", # without children
3227 simplejson.dumps(newkids))
3230 def test_POST_noparent_bad(self):
3231 d = self.shouldHTTPError("POST_noparent_bad",
3233 "/uri accepts only PUT, PUT?t=mkdir, "
3234 "POST?t=upload, and POST?t=mkdir",
3235 self.POST, "/uri?t=bogus")
3238 def test_POST_mkdir_no_parentdir_immutable(self):
3239 (newkids, caps) = self._create_immutable_children()
3240 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3241 def _after_mkdir(res):
3242 self.failUnless(res.startswith("URI:DIR"), res)
3243 n = self.s.create_node_from_uri(res)
3244 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3245 d2.addCallback(lambda ign:
3246 self.failUnlessROChildURIIs(n, u"child-imm",
3248 d2.addCallback(lambda ign:
3249 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3250 caps['unknown_immcap']))
3251 d2.addCallback(lambda ign:
3252 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3254 d2.addCallback(lambda ign:
3255 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3257 d2.addCallback(lambda ign:
3258 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3259 caps['emptydircap']))
3261 d.addCallback(_after_mkdir)
3264 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3265 (newkids, caps) = self._create_initial_children()
3266 d = self.shouldFail2(error.Error,
3267 "test_POST_mkdir_no_parentdir_immutable_bad",
3269 "needed to be immutable but was not",
3271 "/uri?t=mkdir-immutable",
3272 simplejson.dumps(newkids))
3275 def test_welcome_page_mkdir_button(self):
3276 # Fetch the welcome page.
3278 def _after_get_welcome_page(res):
3279 MKDIR_BUTTON_RE = re.compile(
3280 '<form action="([^"]*)" method="post".*'
3281 '<input type="hidden" name="t" value="([^"]*)" />[ ]*'
3282 '<input type="hidden" name="([^"]*)" value="([^"]*)" />[ ]*'
3283 '<input type="submit" class="btn" value="Create a directory[^"]*" />')
3284 html = res.replace('\n', ' ')
3285 mo = MKDIR_BUTTON_RE.search(html)
3286 self.failUnless(mo, html)
3287 formaction = mo.group(1)
3289 formaname = mo.group(3)
3290 formavalue = mo.group(4)
3291 return (formaction, formt, formaname, formavalue)
3292 d.addCallback(_after_get_welcome_page)
3293 def _after_parse_form(res):
3294 (formaction, formt, formaname, formavalue) = res
3295 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3296 d.addCallback(_after_parse_form)
3297 d.addBoth(self.shouldRedirect, None, statuscode='303')
3300 def test_POST_mkdir_replace(self): # return value?
3301 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3302 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3303 d.addCallback(self.failUnlessNodeKeysAre, [])
3306 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3307 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3308 d.addBoth(self.shouldFail, error.Error,
3309 "POST_mkdir_no_replace_queryarg",
3311 "There was already a child by that name, and you asked me "
3312 "to not replace it")
3313 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3314 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3317 def test_POST_mkdir_no_replace_field(self): # return value?
3318 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3320 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3322 "There was already a child by that name, and you asked me "
3323 "to not replace it")
3324 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3325 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3328 def test_POST_mkdir_whendone_field(self):
3329 d = self.POST(self.public_url + "/foo",
3330 t="mkdir", name="newdir", when_done="/THERE")
3331 d.addBoth(self.shouldRedirect, "/THERE")
3332 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3333 d.addCallback(self.failUnlessNodeKeysAre, [])
3336 def test_POST_mkdir_whendone_queryarg(self):
3337 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3338 t="mkdir", name="newdir")
3339 d.addBoth(self.shouldRedirect, "/THERE")
3340 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3341 d.addCallback(self.failUnlessNodeKeysAre, [])
3344 def test_POST_bad_t(self):
3345 d = self.shouldFail2(error.Error, "POST_bad_t",
3347 "POST to a directory with bad t=BOGUS",
3348 self.POST, self.public_url + "/foo", t="BOGUS")
3351 def test_POST_set_children(self, command_name="set_children"):
3352 contents9, n9, newuri9 = self.makefile(9)
3353 contents10, n10, newuri10 = self.makefile(10)
3354 contents11, n11, newuri11 = self.makefile(11)
3357 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3360 "ctime": 1002777696.7564139,
3361 "mtime": 1002777696.7564139
3364 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3367 "ctime": 1002777696.7564139,
3368 "mtime": 1002777696.7564139
3371 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3374 "ctime": 1002777696.7564139,
3375 "mtime": 1002777696.7564139
3378 }""" % (newuri9, newuri10, newuri11)
3380 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3382 d = client.getPage(url, method="POST", postdata=reqbody)
3384 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3385 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3386 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3388 d.addCallback(_then)
3389 d.addErrback(self.dump_error)
3392 def test_POST_set_children_with_hyphen(self):
3393 return self.test_POST_set_children(command_name="set-children")
3395 def test_POST_link_uri(self):
3396 contents, n, newuri = self.makefile(8)
3397 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3398 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3399 d.addCallback(lambda res:
3400 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3404 def test_POST_link_uri_replace(self):
3405 contents, n, newuri = self.makefile(8)
3406 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3407 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3408 d.addCallback(lambda res:
3409 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3413 def test_POST_link_uri_unknown_bad(self):
3414 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3415 d.addBoth(self.shouldFail, error.Error,
3416 "POST_link_uri_unknown_bad",
3418 "unknown cap in a write slot")
3421 def test_POST_link_uri_unknown_ro_good(self):
3422 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3423 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3426 def test_POST_link_uri_unknown_imm_good(self):
3427 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3428 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3431 def test_POST_link_uri_no_replace_queryarg(self):
3432 contents, n, newuri = self.makefile(8)
3433 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3434 name="bar.txt", uri=newuri)
3435 d.addBoth(self.shouldFail, error.Error,
3436 "POST_link_uri_no_replace_queryarg",
3438 "There was already a child by that name, and you asked me "
3439 "to not replace it")
3440 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3441 d.addCallback(self.failUnlessIsBarDotTxt)
3444 def test_POST_link_uri_no_replace_field(self):
3445 contents, n, newuri = self.makefile(8)
3446 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3447 name="bar.txt", uri=newuri)
3448 d.addBoth(self.shouldFail, error.Error,
3449 "POST_link_uri_no_replace_field",
3451 "There was already a child by that name, and you asked me "
3452 "to not replace it")
3453 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3454 d.addCallback(self.failUnlessIsBarDotTxt)
3457 def test_POST_delete(self, command_name='delete'):
3458 d = self._foo_node.list()
3459 def _check_before(children):
3460 self.failUnlessIn(u"bar.txt", children)
3461 d.addCallback(_check_before)
3462 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3463 d.addCallback(lambda res: self._foo_node.list())
3464 def _check_after(children):
3465 self.failIfIn(u"bar.txt", children)
3466 d.addCallback(_check_after)
3469 def test_POST_unlink(self):
3470 return self.test_POST_delete(command_name='unlink')
3472 def test_POST_rename_file(self):
3473 d = self.POST(self.public_url + "/foo", t="rename",
3474 from_name="bar.txt", to_name='wibble.txt')
3475 d.addCallback(lambda res:
3476 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3477 d.addCallback(lambda res:
3478 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3479 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3480 d.addCallback(self.failUnlessIsBarDotTxt)
3481 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3482 d.addCallback(self.failUnlessIsBarJSON)
3485 def test_POST_rename_file_redundant(self):
3486 d = self.POST(self.public_url + "/foo", t="rename",
3487 from_name="bar.txt", to_name='bar.txt')
3488 d.addCallback(lambda res:
3489 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3490 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3491 d.addCallback(self.failUnlessIsBarDotTxt)
3492 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3493 d.addCallback(self.failUnlessIsBarJSON)
3496 def test_POST_rename_file_replace(self):
3497 # rename a file and replace a directory with it
3498 d = self.POST(self.public_url + "/foo", t="rename",
3499 from_name="bar.txt", to_name='empty')
3500 d.addCallback(lambda res:
3501 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3502 d.addCallback(lambda res:
3503 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3504 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3505 d.addCallback(self.failUnlessIsBarDotTxt)
3506 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3507 d.addCallback(self.failUnlessIsBarJSON)
3510 def test_POST_rename_file_no_replace_queryarg(self):
3511 # rename a file and replace a directory with it
3512 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3513 from_name="bar.txt", to_name='empty')
3514 d.addBoth(self.shouldFail, error.Error,
3515 "POST_rename_file_no_replace_queryarg",
3517 "There was already a child by that name, and you asked me "
3518 "to not replace it")
3519 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3520 d.addCallback(self.failUnlessIsEmptyJSON)
3523 def test_POST_rename_file_no_replace_field(self):
3524 # rename a file and replace a directory with it
3525 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3526 from_name="bar.txt", to_name='empty')
3527 d.addBoth(self.shouldFail, error.Error,
3528 "POST_rename_file_no_replace_field",
3530 "There was already a child by that name, and you asked me "
3531 "to not replace it")
3532 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3533 d.addCallback(self.failUnlessIsEmptyJSON)
3536 def test_POST_rename_file_no_replace_same_link(self):
3537 d = self.POST(self.public_url + "/foo", t="rename",
3538 replace="false", from_name="bar.txt", to_name="bar.txt")
3539 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3540 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3541 d.addCallback(self.failUnlessIsBarDotTxt)
3542 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3543 d.addCallback(self.failUnlessIsBarJSON)
3546 def test_POST_rename_file_replace_only_files(self):
3547 d = self.POST(self.public_url + "/foo", t="rename",
3548 replace="only-files", from_name="bar.txt",
3550 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3551 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3552 d.addCallback(self.failUnlessIsBarDotTxt)
3553 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt?t=json"))
3554 d.addCallback(self.failUnlessIsBarJSON)
3557 def test_POST_rename_file_replace_only_files_conflict(self):
3558 d = self.shouldFail2(error.Error, "POST_relink_file_replace_only_files_conflict",
3560 "There was already a child by that name, and you asked me to not replace it.",
3561 self.POST, self.public_url + "/foo", t="relink",
3562 replace="only-files", from_name="bar.txt",
3564 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3565 d.addCallback(self.failUnlessIsBarDotTxt)
3566 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3567 d.addCallback(self.failUnlessIsBarJSON)
3570 def failUnlessIsEmptyJSON(self, res):
3571 data = simplejson.loads(res)
3572 self.failUnlessEqual(data[0], "dirnode", data)
3573 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3575 def test_POST_rename_file_to_slash_fail(self):
3576 d = self.POST(self.public_url + "/foo", t="rename",
3577 from_name="bar.txt", to_name='kirk/spock.txt')
3578 d.addBoth(self.shouldFail, error.Error,
3579 "test_POST_rename_file_to_slash_fail",
3581 "to_name= may not contain a slash",
3583 d.addCallback(lambda res:
3584 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3587 def test_POST_rename_file_from_slash_fail(self):
3588 d = self.POST(self.public_url + "/foo", t="rename",
3589 from_name="sub/bar.txt", to_name='spock.txt')
3590 d.addBoth(self.shouldFail, error.Error,
3591 "test_POST_rename_from_file_slash_fail",
3593 "from_name= may not contain a slash",
3595 d.addCallback(lambda res:
3596 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3599 def test_POST_rename_dir(self):
3600 d = self.POST(self.public_url, t="rename",
3601 from_name="foo", to_name='plunk')
3602 d.addCallback(lambda res:
3603 self.failIfNodeHasChild(self.public_root, u"foo"))
3604 d.addCallback(lambda res:
3605 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3606 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3607 d.addCallback(self.failUnlessIsFooJSON)
3610 def test_POST_relink_file(self):
3611 d = self.POST(self.public_url + "/foo", t="relink",
3612 from_name="bar.txt",
3613 to_dir=self.public_root.get_uri() + "/foo/sub")
3614 d.addCallback(lambda res:
3615 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3616 d.addCallback(lambda res:
3617 self.failUnlessNodeHasChild(self._sub_node, u"bar.txt"))
3618 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3619 d.addCallback(self.failUnlessIsBarDotTxt)
3620 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3621 d.addCallback(self.failUnlessIsBarJSON)
3624 def test_POST_relink_file_new_name(self):
3625 d = self.POST(self.public_url + "/foo", t="relink",
3626 from_name="bar.txt",
3627 to_name="wibble.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
3628 d.addCallback(lambda res:
3629 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3630 d.addCallback(lambda res:
3631 self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3632 d.addCallback(lambda res:
3633 self.failUnlessNodeHasChild(self._sub_node, u"wibble.txt"))
3634 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt"))
3635 d.addCallback(self.failUnlessIsBarDotTxt)
3636 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt?t=json"))
3637 d.addCallback(self.failUnlessIsBarJSON)
3640 def test_POST_relink_file_replace(self):
3641 d = self.POST(self.public_url + "/foo", t="relink",
3642 from_name="bar.txt",
3643 to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
3644 d.addCallback(lambda res:
3645 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3646 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3647 d.addCallback(self.failUnlessIsBarDotTxt)
3648 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3649 d.addCallback(self.failUnlessIsBarJSON)
3652 def test_POST_relink_file_no_replace(self):
3653 d = self.shouldFail2(error.Error, "POST_relink_file_no_replace",
3655 "There was already a child by that name, and you asked me to not replace it",
3656 self.POST, self.public_url + "/foo", t="relink",
3657 replace="false", from_name="bar.txt",
3658 to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
3659 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3660 d.addCallback(self.failUnlessIsBarDotTxt)
3661 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3662 d.addCallback(self.failUnlessIsBarJSON)
3663 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3664 d.addCallback(self.failUnlessIsSubBazDotTxt)
3667 def test_POST_relink_file_no_replace_explicitly_same_link(self):
3668 d = self.POST(self.public_url + "/foo", t="relink",
3669 replace="false", from_name="bar.txt",
3670 to_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo")
3671 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3672 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3673 d.addCallback(self.failUnlessIsBarDotTxt)
3674 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3675 d.addCallback(self.failUnlessIsBarJSON)
3678 def test_POST_relink_file_replace_only_files(self):
3679 d = self.POST(self.public_url + "/foo", t="relink",
3680 replace="only-files", from_name="bar.txt",
3681 to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
3682 d.addCallback(lambda res:
3683 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3684 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3685 d.addCallback(self.failUnlessIsBarDotTxt)
3686 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3687 d.addCallback(self.failUnlessIsBarJSON)
3690 def test_POST_relink_file_replace_only_files_conflict(self):
3691 d = self.shouldFail2(error.Error, "POST_relink_file_replace_only_files_conflict",
3693 "There was already a child by that name, and you asked me to not replace it.",
3694 self.POST, self.public_url + "/foo", t="relink",
3695 replace="only-files", from_name="bar.txt",
3696 to_name="sub", to_dir=self.public_root.get_uri() + "/foo")
3697 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3698 d.addCallback(self.failUnlessIsBarDotTxt)
3699 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3700 d.addCallback(self.failUnlessIsBarJSON)
3703 def test_POST_relink_file_to_slash_fail(self):
3704 d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
3706 "to_name= may not contain a slash",
3707 self.POST, self.public_url + "/foo", t="relink",
3708 from_name="bar.txt",
3709 to_name="slash/fail.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
3710 d.addCallback(lambda res:
3711 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3712 d.addCallback(lambda res:
3713 self.failIfNodeHasChild(self._sub_node, u"slash/fail.txt"))
3714 d.addCallback(lambda ign:
3715 self.shouldFail2(error.Error,
3716 "test_POST_rename_file_slash_fail2",
3718 "from_name= may not contain a slash",
3719 self.POST, self.public_url + "/foo",
3721 from_name="nope/bar.txt",
3723 to_dir=self.public_root.get_uri() + "/foo/sub"))
3726 def test_POST_relink_file_explicitly_same_link(self):
3727 d = self.POST(self.public_url + "/foo", t="relink",
3728 from_name="bar.txt",
3729 to_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo")
3730 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3731 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3732 d.addCallback(self.failUnlessIsBarDotTxt)
3733 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3734 d.addCallback(self.failUnlessIsBarJSON)
3737 def test_POST_relink_file_implicitly_same_link(self):
3738 d = self.POST(self.public_url + "/foo", t="relink",
3739 from_name="bar.txt")
3740 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3741 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3742 d.addCallback(self.failUnlessIsBarDotTxt)
3743 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3744 d.addCallback(self.failUnlessIsBarJSON)
3747 def test_POST_relink_file_same_dir(self):
3748 d = self.POST(self.public_url + "/foo", t="relink",
3749 from_name="bar.txt",
3750 to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo")
3751 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3752 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._sub_node, u"baz.txt"))
3753 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3754 d.addCallback(self.failUnlessIsBarDotTxt)
3755 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt?t=json"))
3756 d.addCallback(self.failUnlessIsBarJSON)
3759 def test_POST_relink_file_bad_replace(self):
3760 d = self.shouldFail2(error.Error, "test_POST_relink_file_bad_replace",
3761 "400 Bad Request", "invalid replace= argument: 'boogabooga'",
3763 self.public_url + "/foo", t="relink",
3764 replace="boogabooga", from_name="bar.txt",
3765 to_dir=self.public_root.get_uri() + "/foo/sub")
3768 def test_POST_relink_file_multi_level(self):
3769 d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
3770 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="relink",
3771 from_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo/sub/level2"))
3772 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3773 d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3774 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
3775 d.addCallback(self.failUnlessIsBarDotTxt)
3776 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt?t=json"))
3777 d.addCallback(self.failUnlessIsBarJSON)
3780 def test_POST_relink_file_to_uri(self):
3781 d = self.POST(self.public_url + "/foo", t="relink", target_type="uri",
3782 from_name="bar.txt", to_dir=self._sub_uri)
3783 d.addCallback(lambda res:
3784 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3785 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3786 d.addCallback(self.failUnlessIsBarDotTxt)
3787 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3788 d.addCallback(self.failUnlessIsBarJSON)
3791 def test_POST_relink_file_to_nonexistent_dir(self):
3792 d = self.shouldFail2(error.Error, "POST_relink_file_to_nonexistent_dir",
3793 "404 Not Found", "No such child: nopechucktesta",
3794 self.POST, self.public_url + "/foo", t="relink",
3795 from_name="bar.txt",
3796 to_dir=self.public_root.get_uri() + "/nopechucktesta")
3799 def test_POST_relink_file_into_file(self):
3800 d = self.shouldFail2(error.Error, "POST_relink_file_into_file",
3801 "400 Bad Request", "to_dir is not a directory",
3802 self.POST, self.public_url + "/foo", t="relink",
3803 from_name="bar.txt",
3804 to_dir=self.public_root.get_uri() + "/foo/baz.txt")
3805 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3806 d.addCallback(self.failUnlessIsBazDotTxt)
3807 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3808 d.addCallback(self.failUnlessIsBarDotTxt)
3809 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3810 d.addCallback(self.failUnlessIsBarJSON)
3813 def test_POST_relink_file_to_bad_uri(self):
3814 d = self.shouldFail2(error.Error, "POST_relink_file_to_bad_uri",
3815 "400 Bad Request", "to_dir is not a directory",
3816 self.POST, self.public_url + "/foo", t="relink",
3817 from_name="bar.txt",
3818 to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
3819 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3820 d.addCallback(self.failUnlessIsBarDotTxt)
3821 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3822 d.addCallback(self.failUnlessIsBarJSON)
3825 def test_POST_relink_dir(self):
3826 d = self.POST(self.public_url + "/foo", t="relink",
3827 from_name="bar.txt",
3828 to_dir=self.public_root.get_uri() + "/foo/empty")
3829 d.addCallback(lambda res: self.POST(self.public_url + "/foo",
3830 t="relink", from_name="empty",
3831 to_dir=self.public_root.get_uri() + "/foo/sub"))
3832 d.addCallback(lambda res:
3833 self.failIfNodeHasChild(self._foo_node, u"empty"))
3834 d.addCallback(lambda res:
3835 self.failUnlessNodeHasChild(self._sub_node, u"empty"))
3836 d.addCallback(lambda res:
3837 self._sub_node.get_child_at_path(u"empty"))
3838 d.addCallback(lambda node:
3839 self.failUnlessNodeHasChild(node, u"bar.txt"))
3840 d.addCallback(lambda res:
3841 self.GET(self.public_url + "/foo/sub/empty/bar.txt"))
3842 d.addCallback(self.failUnlessIsBarDotTxt)
3845 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3846 """ If target is not None then the redirection has to go to target. If
3847 statuscode is not None then the redirection has to be accomplished with
3848 that HTTP status code."""
3849 if not isinstance(res, failure.Failure):
3850 to_where = (target is None) and "somewhere" or ("to " + target)
3851 self.fail("%s: we were expecting to get redirected %s, not get an"
3852 " actual page: %s" % (which, to_where, res))
3853 res.trap(error.PageRedirect)
3854 if statuscode is not None:
3855 self.failUnlessReallyEqual(res.value.status, statuscode,
3856 "%s: not a redirect" % which)
3857 if target is not None:
3858 # the PageRedirect does not seem to capture the uri= query arg
3859 # properly, so we can't check for it.
3860 realtarget = self.webish_url + target
3861 self.failUnlessReallyEqual(res.value.location, realtarget,
3862 "%s: wrong target" % which)
3863 return res.value.location
3865 def test_GET_URI_form(self):
3866 base = "/uri?uri=%s" % self._bar_txt_uri
3867 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3868 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3870 d.addBoth(self.shouldRedirect, targetbase)
3871 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3872 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3873 d.addCallback(lambda res: self.GET(base+"&t=json"))
3874 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3875 d.addCallback(self.log, "about to get file by uri")
3876 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3877 d.addCallback(self.failUnlessIsBarDotTxt)
3878 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3879 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3880 followRedirect=True))
3881 d.addCallback(self.failUnlessIsFooJSON)
3882 d.addCallback(self.log, "got dir by uri")
3886 def test_GET_URI_form_bad(self):
3887 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3888 "400 Bad Request", "GET /uri requires uri=",
3892 def test_GET_rename_form(self):
3893 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3894 followRedirect=True)
3896 self.failUnlessIn('name="when_done" value="."', res)
3897 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3898 self.failUnlessIn(FAVICON_MARKUP, res)
3899 d.addCallback(_check)
3902 def log(self, res, msg):
3903 #print "MSG: %s RES: %s" % (msg, res)
3907 def test_GET_URI_URL(self):
3908 base = "/uri/%s" % self._bar_txt_uri
3910 d.addCallback(self.failUnlessIsBarDotTxt)
3911 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3912 d.addCallback(self.failUnlessIsBarDotTxt)
3913 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3914 d.addCallback(self.failUnlessIsBarDotTxt)
3917 def test_GET_URI_URL_dir(self):
3918 base = "/uri/%s?t=json" % self._foo_uri
3920 d.addCallback(self.failUnlessIsFooJSON)
3923 def test_GET_URI_URL_missing(self):
3924 base = "/uri/%s" % self._bad_file_uri
3925 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3926 http.GONE, None, "NotEnoughSharesError",
3928 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3929 # here? we must arrange for a download to fail after target.open()
3930 # has been called, and then inspect the response to see that it is
3931 # shorter than we expected.
3934 def test_PUT_DIRURL_uri(self):
3935 d = self.s.create_dirnode()
3937 new_uri = dn.get_uri()
3938 # replace /foo with a new (empty) directory
3939 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3940 d.addCallback(lambda res:
3941 self.failUnlessReallyEqual(res.strip(), new_uri))
3942 d.addCallback(lambda res:
3943 self.failUnlessRWChildURIIs(self.public_root,
3947 d.addCallback(_made_dir)
3950 def test_PUT_DIRURL_uri_noreplace(self):
3951 d = self.s.create_dirnode()
3953 new_uri = dn.get_uri()
3954 # replace /foo with a new (empty) directory, but ask that
3955 # replace=false, so it should fail
3956 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3957 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3959 self.public_url + "/foo?t=uri&replace=false",
3961 d.addCallback(lambda res:
3962 self.failUnlessRWChildURIIs(self.public_root,
3966 d.addCallback(_made_dir)
3969 def test_PUT_DIRURL_bad_t(self):
3970 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3971 "400 Bad Request", "PUT to a directory",
3972 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3973 d.addCallback(lambda res:
3974 self.failUnlessRWChildURIIs(self.public_root,
3979 def test_PUT_NEWFILEURL_uri(self):
3980 contents, n, new_uri = self.makefile(8)
3981 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3982 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3983 d.addCallback(lambda res:
3984 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3988 def test_PUT_NEWFILEURL_mdmf(self):
3989 new_contents = self.NEWFILE_CONTENTS * 300000
3990 d = self.PUT(self.public_url + \
3991 "/foo/mdmf.txt?format=mdmf",
3993 d.addCallback(lambda ignored:
3994 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3995 def _got_json(json):
3996 data = simplejson.loads(json)
3998 self.failUnlessIn("format", data)
3999 self.failUnlessEqual(data["format"], "MDMF")
4000 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
4001 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
4002 d.addCallback(_got_json)
4005 def test_PUT_NEWFILEURL_sdmf(self):
4006 new_contents = self.NEWFILE_CONTENTS * 300000
4007 d = self.PUT(self.public_url + \
4008 "/foo/sdmf.txt?format=sdmf",
4010 d.addCallback(lambda ignored:
4011 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
4012 def _got_json(json):
4013 data = simplejson.loads(json)
4015 self.failUnlessIn("format", data)
4016 self.failUnlessEqual(data["format"], "SDMF")
4017 d.addCallback(_got_json)
4020 def test_PUT_NEWFILEURL_bad_format(self):
4021 new_contents = self.NEWFILE_CONTENTS * 300000
4022 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
4023 400, "Bad Request", "Unknown format: foo",
4024 self.PUT, self.public_url + \
4025 "/foo/foo.txt?format=foo",
4028 def test_PUT_NEWFILEURL_uri_replace(self):
4029 contents, n, new_uri = self.makefile(8)
4030 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
4031 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
4032 d.addCallback(lambda res:
4033 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
4037 def test_PUT_NEWFILEURL_uri_no_replace(self):
4038 contents, n, new_uri = self.makefile(8)
4039 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
4040 d.addBoth(self.shouldFail, error.Error,
4041 "PUT_NEWFILEURL_uri_no_replace",
4043 "There was already a child by that name, and you asked me "
4044 "to not replace it")
4047 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
4048 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
4049 d.addBoth(self.shouldFail, error.Error,
4050 "POST_put_uri_unknown_bad",
4052 "unknown cap in a write slot")
4055 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
4056 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
4057 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
4058 u"put-future-ro.txt")
4061 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
4062 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
4063 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
4064 u"put-future-imm.txt")
4067 def test_PUT_NEWFILE_URI(self):
4068 file_contents = "New file contents here\n"
4069 d = self.PUT("/uri", file_contents)
4071 assert isinstance(uri, str), uri
4072 self.failUnlessIn(uri, self.get_all_contents())
4073 self.failUnlessReallyEqual(self.get_all_contents()[uri],
4075 return self.GET("/uri/%s" % uri)
4076 d.addCallback(_check)
4078 self.failUnlessReallyEqual(res, file_contents)
4079 d.addCallback(_check2)
4082 def test_PUT_NEWFILE_URI_not_mutable(self):
4083 file_contents = "New file contents here\n"
4084 d = self.PUT("/uri?mutable=false", file_contents)
4086 assert isinstance(uri, str), uri
4087 self.failUnlessIn(uri, self.get_all_contents())
4088 self.failUnlessReallyEqual(self.get_all_contents()[uri],
4090 return self.GET("/uri/%s" % uri)
4091 d.addCallback(_check)
4093 self.failUnlessReallyEqual(res, file_contents)
4094 d.addCallback(_check2)
4097 def test_PUT_NEWFILE_URI_only_PUT(self):
4098 d = self.PUT("/uri?t=bogus", "")
4099 d.addBoth(self.shouldFail, error.Error,
4100 "PUT_NEWFILE_URI_only_PUT",
4102 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
4105 def test_PUT_NEWFILE_URI_mutable(self):
4106 file_contents = "New file contents here\n"
4107 d = self.PUT("/uri?mutable=true", file_contents)
4108 def _check1(filecap):
4109 filecap = filecap.strip()
4110 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
4111 self.filecap = filecap
4112 u = uri.WriteableSSKFileURI.init_from_string(filecap)
4113 self.failUnlessIn(u.get_storage_index(), self.get_all_contents())
4114 n = self.s.create_node_from_uri(filecap)
4115 return n.download_best_version()
4116 d.addCallback(_check1)
4118 self.failUnlessReallyEqual(data, file_contents)
4119 return self.GET("/uri/%s" % urllib.quote(self.filecap))
4120 d.addCallback(_check2)
4122 self.failUnlessReallyEqual(res, file_contents)
4123 d.addCallback(_check3)
4126 def test_PUT_mkdir(self):
4127 d = self.PUT("/uri?t=mkdir", "")
4129 n = self.s.create_node_from_uri(uri.strip())
4130 d2 = self.failUnlessNodeKeysAre(n, [])
4131 d2.addCallback(lambda res:
4132 self.GET("/uri/%s?t=json" % uri))
4134 d.addCallback(_check)
4135 d.addCallback(self.failUnlessIsEmptyJSON)
4138 def test_PUT_mkdir_mdmf(self):
4139 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
4141 u = uri.from_string(res)
4142 # Check that this is an MDMF writecap
4143 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
4147 def test_PUT_mkdir_sdmf(self):
4148 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
4150 u = uri.from_string(res)
4151 self.failUnlessIsInstance(u, uri.DirectoryURI)
4155 def test_PUT_mkdir_bad_format(self):
4156 return self.shouldHTTPError("PUT_mkdir_bad_format",
4157 400, "Bad Request", "Unknown format: foo",
4158 self.PUT, "/uri?t=mkdir&format=foo",
4161 def test_POST_check(self):
4162 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
4164 # this returns a string form of the results, which are probably
4165 # None since we're using fake filenodes.
4166 # TODO: verify that the check actually happened, by changing
4167 # FakeCHKFileNode to count how many times .check() has been
4170 d.addCallback(_done)
4174 def test_PUT_update_at_offset(self):
4175 file_contents = "test file" * 100000 # about 900 KiB
4176 d = self.PUT("/uri?mutable=true", file_contents)
4178 self.filecap = filecap
4179 new_data = file_contents[:100]
4180 new = "replaced and so on"
4182 new_data += file_contents[len(new_data):]
4183 assert len(new_data) == len(file_contents)
4184 self.new_data = new_data
4185 d.addCallback(_then)
4186 d.addCallback(lambda ignored:
4187 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
4188 "replaced and so on"))
4189 def _get_data(filecap):
4190 n = self.s.create_node_from_uri(filecap)
4191 return n.download_best_version()
4192 d.addCallback(_get_data)
4193 d.addCallback(lambda results:
4194 self.failUnlessEqual(results, self.new_data))
4195 # Now try appending things to the file
4196 d.addCallback(lambda ignored:
4197 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
4199 d.addCallback(_get_data)
4200 d.addCallback(lambda results:
4201 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
4202 # and try replacing the beginning of the file
4203 d.addCallback(lambda ignored:
4204 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
4205 d.addCallback(_get_data)
4206 d.addCallback(lambda results:
4207 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
4210 def test_PUT_update_at_invalid_offset(self):
4211 file_contents = "test file" * 100000 # about 900 KiB
4212 d = self.PUT("/uri?mutable=true", file_contents)
4214 self.filecap = filecap
4215 d.addCallback(_then)
4216 # Negative offsets should cause an error.
4217 d.addCallback(lambda ignored:
4218 self.shouldHTTPError("PUT_update_at_invalid_offset",
4222 "/uri/%s?offset=-1" % self.filecap,
4226 def test_PUT_update_at_offset_immutable(self):
4227 file_contents = "Test file" * 100000
4228 d = self.PUT("/uri", file_contents)
4230 self.filecap = filecap
4231 d.addCallback(_then)
4232 d.addCallback(lambda ignored:
4233 self.shouldHTTPError("PUT_update_at_offset_immutable",
4237 "/uri/%s?offset=50" % self.filecap,
4242 def test_bad_method(self):
4243 url = self.webish_url + self.public_url + "/foo/bar.txt"
4244 d = self.shouldHTTPError("bad_method",
4245 501, "Not Implemented",
4246 "I don't know how to treat a BOGUS request.",
4247 client.getPage, url, method="BOGUS")
4250 def test_short_url(self):
4251 url = self.webish_url + "/uri"
4252 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
4253 "I don't know how to treat a DELETE request.",
4254 client.getPage, url, method="DELETE")
4257 def test_ophandle_bad(self):
4258 url = self.webish_url + "/operations/bogus?t=status"
4259 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
4260 "unknown/expired handle 'bogus'",
4261 client.getPage, url)
4264 def test_ophandle_cancel(self):
4265 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
4266 followRedirect=True)
4267 d.addCallback(lambda ignored:
4268 self.GET("/operations/128?t=status&output=JSON"))
4270 data = simplejson.loads(res)
4271 self.failUnless("finished" in data, res)
4272 monitor = self.ws.root.child_operations.handles["128"][0]
4273 d = self.POST("/operations/128?t=cancel&output=JSON")
4275 data = simplejson.loads(res)
4276 self.failUnless("finished" in data, res)
4277 # t=cancel causes the handle to be forgotten
4278 self.failUnless(monitor.is_cancelled())
4279 d.addCallback(_check2)
4281 d.addCallback(_check1)
4282 d.addCallback(lambda ignored:
4283 self.shouldHTTPError("ophandle_cancel",
4284 404, "404 Not Found",
4285 "unknown/expired handle '128'",
4287 "/operations/128?t=status&output=JSON"))
4290 def test_ophandle_retainfor(self):
4291 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
4292 followRedirect=True)
4293 d.addCallback(lambda ignored:
4294 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
4296 data = simplejson.loads(res)
4297 self.failUnless("finished" in data, res)
4298 d.addCallback(_check1)
4299 # the retain-for=0 will cause the handle to be expired very soon
4300 d.addCallback(lambda ign:
4301 self.clock.advance(2.0))
4302 d.addCallback(lambda ignored:
4303 self.shouldHTTPError("ophandle_retainfor",
4304 404, "404 Not Found",
4305 "unknown/expired handle '129'",
4307 "/operations/129?t=status&output=JSON"))
4310 def test_ophandle_release_after_complete(self):
4311 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
4312 followRedirect=True)
4313 d.addCallback(self.wait_for_operation, "130")
4314 d.addCallback(lambda ignored:
4315 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
4316 # the release-after-complete=true will cause the handle to be expired
4317 d.addCallback(lambda ignored:
4318 self.shouldHTTPError("ophandle_release_after_complete",
4319 404, "404 Not Found",
4320 "unknown/expired handle '130'",
4322 "/operations/130?t=status&output=JSON"))
4325 def test_uncollected_ophandle_expiration(self):
4326 # uncollected ophandles should expire after 4 days
4327 def _make_uncollected_ophandle(ophandle):
4328 d = self.POST(self.public_url +
4329 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
4330 followRedirect=False)
4331 # When we start the operation, the webapi server will want
4332 # to redirect us to the page for the ophandle, so we get
4333 # confirmation that the operation has started. If the
4334 # manifest operation has finished by the time we get there,
4335 # following that redirect (by setting followRedirect=True
4336 # above) has the side effect of collecting the ophandle that
4337 # we've just created, which means that we can't use the
4338 # ophandle to test the uncollected timeout anymore. So,
4339 # instead, catch the 302 here and don't follow it.
4340 d.addBoth(self.should302, "uncollected_ophandle_creation")
4342 # Create an ophandle, don't collect it, then advance the clock by
4343 # 4 days - 1 second and make sure that the ophandle is still there.
4344 d = _make_uncollected_ophandle(131)
4345 d.addCallback(lambda ign:
4346 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
4347 d.addCallback(lambda ign:
4348 self.GET("/operations/131?t=status&output=JSON"))
4350 data = simplejson.loads(res)
4351 self.failUnless("finished" in data, res)
4352 d.addCallback(_check1)
4353 # Create an ophandle, don't collect it, then try to collect it
4354 # after 4 days. It should be gone.
4355 d.addCallback(lambda ign:
4356 _make_uncollected_ophandle(132))
4357 d.addCallback(lambda ign:
4358 self.clock.advance(96*60*60))
4359 d.addCallback(lambda ign:
4360 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
4361 404, "404 Not Found",
4362 "unknown/expired handle '132'",
4364 "/operations/132?t=status&output=JSON"))
4367 def test_collected_ophandle_expiration(self):
4368 # collected ophandles should expire after 1 day
4369 def _make_collected_ophandle(ophandle):
4370 d = self.POST(self.public_url +
4371 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
4372 followRedirect=True)
4373 # By following the initial redirect, we collect the ophandle
4374 # we've just created.
4376 # Create a collected ophandle, then collect it after 23 hours
4377 # and 59 seconds to make sure that it is still there.
4378 d = _make_collected_ophandle(133)
4379 d.addCallback(lambda ign:
4380 self.clock.advance((24*60*60) - 1))
4381 d.addCallback(lambda ign:
4382 self.GET("/operations/133?t=status&output=JSON"))
4384 data = simplejson.loads(res)
4385 self.failUnless("finished" in data, res)
4386 d.addCallback(_check1)
4387 # Create another uncollected ophandle, then try to collect it
4388 # after 24 hours to make sure that it is gone.
4389 d.addCallback(lambda ign:
4390 _make_collected_ophandle(134))
4391 d.addCallback(lambda ign:
4392 self.clock.advance(24*60*60))
4393 d.addCallback(lambda ign:
4394 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
4395 404, "404 Not Found",
4396 "unknown/expired handle '134'",
4398 "/operations/134?t=status&output=JSON"))
4401 def test_incident(self):
4402 d = self.POST("/report_incident", details="eek")
4404 self.failIfIn("<html>", res)
4405 self.failUnlessIn("An incident report has been saved", res)
4406 d.addCallback(_done)
4409 def test_static(self):
4410 webdir = os.path.join(self.staticdir, "subdir")
4411 fileutil.make_dirs(webdir)
4412 f = open(os.path.join(webdir, "hello.txt"), "wb")
4416 d = self.GET("/static/subdir/hello.txt")
4418 self.failUnlessReallyEqual(res, "hello")
4419 d.addCallback(_check)
4423 class IntroducerWeb(unittest.TestCase):
4428 d = defer.succeed(None)
4430 d.addCallback(lambda ign: self.node.stopService())
4431 d.addCallback(flushEventualQueue)
4434 def test_welcome(self):
4435 basedir = "web.IntroducerWeb.test_welcome"
4437 cfg = "\n".join(["[node]",
4438 "tub.location = 127.0.0.1:1",
4441 fileutil.write(os.path.join(basedir, "tahoe.cfg"), cfg)
4442 self.node = IntroducerNode(basedir)
4443 self.ws = self.node.getServiceNamed("webish")
4445 d = fireEventually(None)
4446 d.addCallback(lambda ign: self.node.startService())
4447 d.addCallback(lambda ign: self.node.when_tub_ready())
4449 d.addCallback(lambda ign: self.GET("/"))
4451 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
4452 self.failUnlessIn(FAVICON_MARKUP, res)
4453 self.failUnlessIn('Page rendered at', res)
4454 self.failUnlessIn('Tahoe-LAFS code imported from:', res)
4455 d.addCallback(_check)
4458 def GET(self, urlpath, followRedirect=False, return_response=False,
4460 # if return_response=True, this fires with (data, statuscode,
4461 # respheaders) instead of just data.
4462 assert not isinstance(urlpath, unicode)
4463 url = self.ws.getURL().rstrip('/') + urlpath
4464 factory = HTTPClientGETFactory(url, method="GET",
4465 followRedirect=followRedirect, **kwargs)
4466 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
4467 d = factory.deferred
4468 def _got_data(data):
4469 return (data, factory.status, factory.response_headers)
4471 d.addCallback(_got_data)
4472 return factory.deferred
4475 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4476 def test_load_file(self):
4477 # This will raise an exception unless a well-formed XML file is found under that name.
4478 common.getxmlfile('directory.xhtml').load()
4480 def test_parse_replace_arg(self):
4481 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4482 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4483 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4485 self.failUnlessRaises(common.WebError, common.parse_replace_arg, "only_fles")
4487 def test_abbreviate_time(self):
4488 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4489 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4490 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4491 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4492 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4493 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4495 def test_compute_rate(self):
4496 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4497 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4498 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4499 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4500 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4501 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4502 self.shouldFail(AssertionError, "test_compute_rate", "",
4503 common.compute_rate, -100, 10)
4504 self.shouldFail(AssertionError, "test_compute_rate", "",
4505 common.compute_rate, 100, -10)
4508 rate = common.compute_rate(10*1000*1000, 1)
4509 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4511 def test_abbreviate_rate(self):
4512 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4513 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4514 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4515 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4517 def test_abbreviate_size(self):
4518 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4519 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4520 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4521 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4522 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4524 def test_plural(self):
4526 return "%d second%s" % (s, status.plural(s))
4527 self.failUnlessReallyEqual(convert(0), "0 seconds")
4528 self.failUnlessReallyEqual(convert(1), "1 second")
4529 self.failUnlessReallyEqual(convert(2), "2 seconds")
4531 return "has share%s: %s" % (status.plural(s), ",".join(s))
4532 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4533 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4534 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4537 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4539 def CHECK(self, ign, which, args, clientnum=0):
4540 fileurl = self.fileurls[which]
4541 url = fileurl + "?" + args
4542 return self.GET(url, method="POST", clientnum=clientnum)
4544 def test_filecheck(self):
4545 self.basedir = "web/Grid/filecheck"
4547 c0 = self.g.clients[0]
4550 d = c0.upload(upload.Data(DATA, convergence=""))
4551 def _stash_uri(ur, which):
4552 self.uris[which] = ur.get_uri()
4553 d.addCallback(_stash_uri, "good")
4554 d.addCallback(lambda ign:
4555 c0.upload(upload.Data(DATA+"1", convergence="")))
4556 d.addCallback(_stash_uri, "sick")
4557 d.addCallback(lambda ign:
4558 c0.upload(upload.Data(DATA+"2", convergence="")))
4559 d.addCallback(_stash_uri, "dead")
4560 def _stash_mutable_uri(n, which):
4561 self.uris[which] = n.get_uri()
4562 assert isinstance(self.uris[which], str)
4563 d.addCallback(lambda ign:
4564 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4565 d.addCallback(_stash_mutable_uri, "corrupt")
4566 d.addCallback(lambda ign:
4567 c0.upload(upload.Data("literal", convergence="")))
4568 d.addCallback(_stash_uri, "small")
4569 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4570 d.addCallback(_stash_mutable_uri, "smalldir")
4572 def _compute_fileurls(ignored):
4574 for which in self.uris:
4575 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4576 d.addCallback(_compute_fileurls)
4578 def _clobber_shares(ignored):
4579 good_shares = self.find_uri_shares(self.uris["good"])
4580 self.failUnlessReallyEqual(len(good_shares), 10)
4581 sick_shares = self.find_uri_shares(self.uris["sick"])
4582 os.unlink(sick_shares[0][2])
4583 dead_shares = self.find_uri_shares(self.uris["dead"])
4584 for i in range(1, 10):
4585 os.unlink(dead_shares[i][2])
4586 c_shares = self.find_uri_shares(self.uris["corrupt"])
4587 cso = CorruptShareOptions()
4588 cso.stdout = StringIO()
4589 cso.parseOptions([c_shares[0][2]])
4591 d.addCallback(_clobber_shares)
4593 d.addCallback(self.CHECK, "good", "t=check")
4594 def _got_html_good(res):
4595 self.failUnlessIn("Healthy", res)
4596 self.failIfIn("Not Healthy", res)
4597 self.failUnlessIn(FAVICON_MARKUP, res)
4598 d.addCallback(_got_html_good)
4599 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4600 def _got_html_good_return_to(res):
4601 self.failUnlessIn("Healthy", res)
4602 self.failIfIn("Not Healthy", res)
4603 self.failUnlessIn('<a href="somewhere">Return to file', res)
4604 d.addCallback(_got_html_good_return_to)
4605 d.addCallback(self.CHECK, "good", "t=check&output=json")
4606 def _got_json_good(res):
4607 r = simplejson.loads(res)
4608 self.failUnlessEqual(r["summary"], "Healthy")
4609 self.failUnless(r["results"]["healthy"])
4610 self.failIfIn("needs-rebalancing", r["results"])
4611 self.failUnless(r["results"]["recoverable"])
4612 d.addCallback(_got_json_good)
4614 d.addCallback(self.CHECK, "small", "t=check")
4615 def _got_html_small(res):
4616 self.failUnlessIn("Literal files are always healthy", res)
4617 self.failIfIn("Not Healthy", res)
4618 d.addCallback(_got_html_small)
4619 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4620 def _got_html_small_return_to(res):
4621 self.failUnlessIn("Literal files are always healthy", res)
4622 self.failIfIn("Not Healthy", res)
4623 self.failUnlessIn('<a href="somewhere">Return to file', res)
4624 d.addCallback(_got_html_small_return_to)
4625 d.addCallback(self.CHECK, "small", "t=check&output=json")
4626 def _got_json_small(res):
4627 r = simplejson.loads(res)
4628 self.failUnlessEqual(r["storage-index"], "")
4629 self.failUnless(r["results"]["healthy"])
4630 d.addCallback(_got_json_small)
4632 d.addCallback(self.CHECK, "smalldir", "t=check")
4633 def _got_html_smalldir(res):
4634 self.failUnlessIn("Literal files are always healthy", res)
4635 self.failIfIn("Not Healthy", res)
4636 d.addCallback(_got_html_smalldir)
4637 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4638 def _got_json_smalldir(res):
4639 r = simplejson.loads(res)
4640 self.failUnlessEqual(r["storage-index"], "")
4641 self.failUnless(r["results"]["healthy"])
4642 d.addCallback(_got_json_smalldir)
4644 d.addCallback(self.CHECK, "sick", "t=check")
4645 def _got_html_sick(res):
4646 self.failUnlessIn("Not Healthy", res)
4647 d.addCallback(_got_html_sick)
4648 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4649 def _got_json_sick(res):
4650 r = simplejson.loads(res)
4651 self.failUnlessEqual(r["summary"],
4652 "Not Healthy: 9 shares (enc 3-of-10)")
4653 self.failIf(r["results"]["healthy"])
4654 self.failUnless(r["results"]["recoverable"])
4655 self.failIfIn("needs-rebalancing", r["results"])
4656 d.addCallback(_got_json_sick)
4658 d.addCallback(self.CHECK, "dead", "t=check")
4659 def _got_html_dead(res):
4660 self.failUnlessIn("Not Healthy", res)
4661 d.addCallback(_got_html_dead)
4662 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4663 def _got_json_dead(res):
4664 r = simplejson.loads(res)
4665 self.failUnlessEqual(r["summary"],
4666 "Not Healthy: 1 shares (enc 3-of-10)")
4667 self.failIf(r["results"]["healthy"])
4668 self.failIf(r["results"]["recoverable"])
4669 self.failIfIn("needs-rebalancing", r["results"])
4670 d.addCallback(_got_json_dead)
4672 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4673 def _got_html_corrupt(res):
4674 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4675 d.addCallback(_got_html_corrupt)
4676 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4677 def _got_json_corrupt(res):
4678 r = simplejson.loads(res)
4679 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4680 self.failIf(r["results"]["healthy"])
4681 self.failUnless(r["results"]["recoverable"])
4682 self.failIfIn("needs-rebalancing", r["results"])
4683 self.failUnlessReallyEqual(r["results"]["count-happiness"], 9)
4684 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4685 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4686 d.addCallback(_got_json_corrupt)
4688 d.addErrback(self.explain_web_error)
4691 def test_repair_html(self):
4692 self.basedir = "web/Grid/repair_html"
4694 c0 = self.g.clients[0]
4697 d = c0.upload(upload.Data(DATA, convergence=""))
4698 def _stash_uri(ur, which):
4699 self.uris[which] = ur.get_uri()
4700 d.addCallback(_stash_uri, "good")
4701 d.addCallback(lambda ign:
4702 c0.upload(upload.Data(DATA+"1", convergence="")))
4703 d.addCallback(_stash_uri, "sick")
4704 d.addCallback(lambda ign:
4705 c0.upload(upload.Data(DATA+"2", convergence="")))
4706 d.addCallback(_stash_uri, "dead")
4707 def _stash_mutable_uri(n, which):
4708 self.uris[which] = n.get_uri()
4709 assert isinstance(self.uris[which], str)
4710 d.addCallback(lambda ign:
4711 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4712 d.addCallback(_stash_mutable_uri, "corrupt")
4714 def _compute_fileurls(ignored):
4716 for which in self.uris:
4717 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4718 d.addCallback(_compute_fileurls)
4720 def _clobber_shares(ignored):
4721 good_shares = self.find_uri_shares(self.uris["good"])
4722 self.failUnlessReallyEqual(len(good_shares), 10)
4723 sick_shares = self.find_uri_shares(self.uris["sick"])
4724 os.unlink(sick_shares[0][2])
4725 dead_shares = self.find_uri_shares(self.uris["dead"])
4726 for i in range(1, 10):
4727 os.unlink(dead_shares[i][2])
4728 c_shares = self.find_uri_shares(self.uris["corrupt"])
4729 cso = CorruptShareOptions()
4730 cso.stdout = StringIO()
4731 cso.parseOptions([c_shares[0][2]])
4733 d.addCallback(_clobber_shares)
4735 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4736 def _got_html_good(res):
4737 self.failUnlessIn("Healthy", res)
4738 self.failIfIn("Not Healthy", res)
4739 self.failUnlessIn("No repair necessary", res)
4740 self.failUnlessIn(FAVICON_MARKUP, res)
4741 d.addCallback(_got_html_good)
4743 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4744 def _got_html_sick(res):
4745 self.failUnlessIn("Healthy : healthy", res)
4746 self.failIfIn("Not Healthy", res)
4747 self.failUnlessIn("Repair successful", res)
4748 d.addCallback(_got_html_sick)
4750 # repair of a dead file will fail, of course, but it isn't yet
4751 # clear how this should be reported. Right now it shows up as
4754 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4755 #def _got_html_dead(res):
4757 # self.failUnlessIn("Healthy : healthy", res)
4758 # self.failIfIn("Not Healthy", res)
4759 # self.failUnlessIn("No repair necessary", res)
4760 #d.addCallback(_got_html_dead)
4762 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4763 def _got_html_corrupt(res):
4764 self.failUnlessIn("Healthy : Healthy", res)
4765 self.failIfIn("Not Healthy", res)
4766 self.failUnlessIn("Repair successful", res)
4767 d.addCallback(_got_html_corrupt)
4769 d.addErrback(self.explain_web_error)
4772 def test_repair_json(self):
4773 self.basedir = "web/Grid/repair_json"
4775 c0 = self.g.clients[0]
4778 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4779 def _stash_uri(ur, which):
4780 self.uris[which] = ur.get_uri()
4781 d.addCallback(_stash_uri, "sick")
4783 def _compute_fileurls(ignored):
4785 for which in self.uris:
4786 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4787 d.addCallback(_compute_fileurls)
4789 def _clobber_shares(ignored):
4790 sick_shares = self.find_uri_shares(self.uris["sick"])
4791 os.unlink(sick_shares[0][2])
4792 d.addCallback(_clobber_shares)
4794 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4795 def _got_json_sick(res):
4796 r = simplejson.loads(res)
4797 self.failUnlessReallyEqual(r["repair-attempted"], True)
4798 self.failUnlessReallyEqual(r["repair-successful"], True)
4799 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4800 "Not Healthy: 9 shares (enc 3-of-10)")
4801 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4802 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4803 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4804 d.addCallback(_got_json_sick)
4806 d.addErrback(self.explain_web_error)
4809 def test_unknown(self, immutable=False):
4810 self.basedir = "web/Grid/unknown"
4812 self.basedir = "web/Grid/unknown-immutable"
4815 c0 = self.g.clients[0]
4819 # the future cap format may contain slashes, which must be tolerated
4820 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4824 name = u"future-imm"
4825 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4826 d = c0.create_immutable_dirnode({name: (future_node, {})})
4829 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4830 d = c0.create_dirnode()
4832 def _stash_root_and_create_file(n):
4834 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4835 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4837 return self.rootnode.set_node(name, future_node)
4838 d.addCallback(_stash_root_and_create_file)
4840 # make sure directory listing tolerates unknown nodes
4841 d.addCallback(lambda ign: self.GET(self.rooturl))
4842 def _check_directory_html(res, expected_type_suffix):
4843 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4844 '<td>%s</td>' % (expected_type_suffix, str(name)),
4846 self.failUnless(re.search(pattern, res), res)
4847 # find the More Info link for name, should be relative
4848 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4849 info_url = mo.group(1)
4850 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4852 d.addCallback(_check_directory_html, "-IMM")
4854 d.addCallback(_check_directory_html, "")
4856 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4857 def _check_directory_json(res, expect_rw_uri):
4858 data = simplejson.loads(res)
4859 self.failUnlessEqual(data[0], "dirnode")
4860 f = data[1]["children"][name]
4861 self.failUnlessEqual(f[0], "unknown")
4863 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4865 self.failIfIn("rw_uri", f[1])
4867 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4869 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4870 self.failUnlessIn("metadata", f[1])
4871 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4873 def _check_info(res, expect_rw_uri, expect_ro_uri):
4874 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4876 self.failUnlessIn(unknown_rwcap, res)
4879 self.failUnlessIn(unknown_immcap, res)
4881 self.failUnlessIn(unknown_rocap, res)
4883 self.failIfIn(unknown_rocap, res)
4884 self.failIfIn("Raw data as", res)
4885 self.failIfIn("Directory writecap", res)
4886 self.failIfIn("Checker Operations", res)
4887 self.failIfIn("Mutable File Operations", res)
4888 self.failIfIn("Directory Operations", res)
4890 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4891 # why they fail. Possibly related to ticket #922.
4893 d.addCallback(lambda ign: self.GET(expected_info_url))
4894 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4895 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4896 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4898 def _check_json(res, expect_rw_uri):
4899 data = simplejson.loads(res)
4900 self.failUnlessEqual(data[0], "unknown")
4902 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4904 self.failIfIn("rw_uri", data[1])
4907 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4908 self.failUnlessReallyEqual(data[1]["mutable"], False)
4910 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4911 self.failUnlessReallyEqual(data[1]["mutable"], True)
4913 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4914 self.failIfIn("mutable", data[1])
4916 # TODO: check metadata contents
4917 self.failUnlessIn("metadata", data[1])
4919 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4920 d.addCallback(_check_json, expect_rw_uri=not immutable)
4922 # and make sure that a read-only version of the directory can be
4923 # rendered too. This version will not have unknown_rwcap, whether
4924 # or not future_node was immutable.
4925 d.addCallback(lambda ign: self.GET(self.rourl))
4927 d.addCallback(_check_directory_html, "-IMM")
4929 d.addCallback(_check_directory_html, "-RO")
4931 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4932 d.addCallback(_check_directory_json, expect_rw_uri=False)
4934 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4935 d.addCallback(_check_json, expect_rw_uri=False)
4937 # TODO: check that getting t=info from the Info link in the ro directory
4938 # works, and does not include the writecap URI.
4941 def test_immutable_unknown(self):
4942 return self.test_unknown(immutable=True)
4944 def test_mutant_dirnodes_are_omitted(self):
4945 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4948 c = self.g.clients[0]
4953 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4954 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4955 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4957 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4958 # test the dirnode and web layers separately.
4960 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4961 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4962 # When the directory is read, the mutants should be silently disposed of, leaving
4963 # their lonely sibling.
4964 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4965 # because immutable directories don't have a writecap and therefore that field
4966 # isn't (and can't be) decrypted.
4967 # TODO: The field still exists in the netstring. Technically we should check what
4968 # happens if something is put there (_unpack_contents should raise ValueError),
4969 # but that can wait.
4971 lonely_child = nm.create_from_cap(lonely_uri)
4972 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4973 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4975 def _by_hook_or_by_crook():
4977 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4978 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4980 mutant_write_in_ro_child.get_write_uri = lambda: None
4981 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4983 kids = {u"lonely": (lonely_child, {}),
4984 u"ro": (mutant_ro_child, {}),
4985 u"write-in-ro": (mutant_write_in_ro_child, {}),
4987 d = c.create_immutable_dirnode(kids)
4990 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4991 self.failIf(dn.is_mutable())
4992 self.failUnless(dn.is_readonly())
4993 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4994 self.failIf(hasattr(dn._node, 'get_writekey'))
4996 self.failUnlessIn("RO-IMM", rep)
4998 self.failUnlessIn("CHK", cap.to_string())
5001 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
5002 return download_to_data(dn._node)
5003 d.addCallback(_created)
5005 def _check_data(data):
5006 # Decode the netstring representation of the directory to check that all children
5007 # are present. This is a bit of an abstraction violation, but there's not really
5008 # any other way to do it given that the real DirectoryNode._unpack_contents would
5009 # strip the mutant children out (which is what we're trying to test, later).
5012 while position < len(data):
5013 entries, position = split_netstring(data, 1, position)
5015 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
5016 name = name_utf8.decode("utf-8")
5017 self.failUnlessEqual(rwcapdata, "")
5018 self.failUnlessIn(name, kids)
5019 (expected_child, ign) = kids[name]
5020 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
5023 self.failUnlessReallyEqual(numkids, 3)
5024 return self.rootnode.list()
5025 d.addCallback(_check_data)
5027 # Now when we use the real directory listing code, the mutants should be absent.
5028 def _check_kids(children):
5029 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
5030 lonely_node, lonely_metadata = children[u"lonely"]
5032 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
5033 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
5034 d.addCallback(_check_kids)
5036 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
5037 d.addCallback(lambda n: n.list())
5038 d.addCallback(_check_kids) # again with dirnode recreated from cap
5040 # Make sure the lonely child can be listed in HTML...
5041 d.addCallback(lambda ign: self.GET(self.rooturl))
5042 def _check_html(res):
5043 self.failIfIn("URI:SSK", res)
5044 get_lonely = "".join([r'<td>FILE</td>',
5046 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
5048 r'\s+<td align="right">%d</td>' % len("one"),
5050 self.failUnless(re.search(get_lonely, res), res)
5052 # find the More Info link for name, should be relative
5053 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
5054 info_url = mo.group(1)
5055 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
5056 d.addCallback(_check_html)
5059 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
5060 def _check_json(res):
5061 data = simplejson.loads(res)
5062 self.failUnlessEqual(data[0], "dirnode")
5063 listed_children = data[1]["children"]
5064 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
5065 ll_type, ll_data = listed_children[u"lonely"]
5066 self.failUnlessEqual(ll_type, "filenode")
5067 self.failIfIn("rw_uri", ll_data)
5068 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
5069 d.addCallback(_check_json)
5072 def test_deep_check(self):
5073 self.basedir = "web/Grid/deep_check"
5075 c0 = self.g.clients[0]
5079 d = c0.create_dirnode()
5080 def _stash_root_and_create_file(n):
5082 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5083 return n.add_file(u"good", upload.Data(DATA, convergence=""))
5084 d.addCallback(_stash_root_and_create_file)
5085 def _stash_uri(fn, which):
5086 self.uris[which] = fn.get_uri()
5088 d.addCallback(_stash_uri, "good")
5089 d.addCallback(lambda ign:
5090 self.rootnode.add_file(u"small",
5091 upload.Data("literal",
5093 d.addCallback(_stash_uri, "small")
5094 d.addCallback(lambda ign:
5095 self.rootnode.add_file(u"sick",
5096 upload.Data(DATA+"1",
5098 d.addCallback(_stash_uri, "sick")
5100 # this tests that deep-check and stream-manifest will ignore
5101 # UnknownNode instances. Hopefully this will also cover deep-stats.
5102 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
5103 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
5105 def _clobber_shares(ignored):
5106 self.delete_shares_numbered(self.uris["sick"], [0,1])
5107 d.addCallback(_clobber_shares)
5115 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
5118 units = [simplejson.loads(line)
5119 for line in res.splitlines()
5122 print "response is:", res
5123 print "undecodeable line was '%s'" % line
5125 self.failUnlessReallyEqual(len(units), 5+1)
5126 # should be parent-first
5128 self.failUnlessEqual(u0["path"], [])
5129 self.failUnlessEqual(u0["type"], "directory")
5130 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
5131 u0cr = u0["check-results"]
5132 self.failUnlessReallyEqual(u0cr["results"]["count-happiness"], 10)
5133 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
5135 ugood = [u for u in units
5136 if u["type"] == "file" and u["path"] == [u"good"]][0]
5137 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
5138 ugoodcr = ugood["check-results"]
5139 self.failUnlessReallyEqual(ugoodcr["results"]["count-happiness"], 10)
5140 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
5143 self.failUnlessEqual(stats["type"], "stats")
5145 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
5146 self.failUnlessReallyEqual(s["count-literal-files"], 1)
5147 self.failUnlessReallyEqual(s["count-directories"], 1)
5148 self.failUnlessReallyEqual(s["count-unknown"], 1)
5149 d.addCallback(_done)
5151 d.addCallback(self.CHECK, "root", "t=stream-manifest")
5152 def _check_manifest(res):
5153 self.failUnless(res.endswith("\n"))
5154 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
5155 self.failUnlessReallyEqual(len(units), 5+1)
5156 self.failUnlessEqual(units[-1]["type"], "stats")
5158 self.failUnlessEqual(first["path"], [])
5159 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
5160 self.failUnlessEqual(first["type"], "directory")
5161 stats = units[-1]["stats"]
5162 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
5163 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
5164 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
5165 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
5166 self.failUnlessReallyEqual(stats["count-unknown"], 1)
5167 d.addCallback(_check_manifest)
5169 # now add root/subdir and root/subdir/grandchild, then make subdir
5170 # unrecoverable, then see what happens
5172 d.addCallback(lambda ign:
5173 self.rootnode.create_subdirectory(u"subdir"))
5174 d.addCallback(_stash_uri, "subdir")
5175 d.addCallback(lambda subdir_node:
5176 subdir_node.add_file(u"grandchild",
5177 upload.Data(DATA+"2",
5179 d.addCallback(_stash_uri, "grandchild")
5181 d.addCallback(lambda ign:
5182 self.delete_shares_numbered(self.uris["subdir"],
5190 # root/subdir [unrecoverable]
5191 # root/subdir/grandchild
5193 # how should a streaming-JSON API indicate fatal error?
5194 # answer: emit ERROR: instead of a JSON string
5196 d.addCallback(self.CHECK, "root", "t=stream-manifest")
5197 def _check_broken_manifest(res):
5198 lines = res.splitlines()
5200 for (i,line) in enumerate(lines)
5201 if line.startswith("ERROR:")]
5203 self.fail("no ERROR: in output: %s" % (res,))
5204 first_error = error_lines[0]
5205 error_line = lines[first_error]
5206 error_msg = lines[first_error+1:]
5207 error_msg_s = "\n".join(error_msg) + "\n"
5208 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
5210 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
5211 units = [simplejson.loads(line) for line in lines[:first_error]]
5212 self.failUnlessReallyEqual(len(units), 6) # includes subdir
5213 last_unit = units[-1]
5214 self.failUnlessEqual(last_unit["path"], ["subdir"])
5215 d.addCallback(_check_broken_manifest)
5217 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
5218 def _check_broken_deepcheck(res):
5219 lines = res.splitlines()
5221 for (i,line) in enumerate(lines)
5222 if line.startswith("ERROR:")]
5224 self.fail("no ERROR: in output: %s" % (res,))
5225 first_error = error_lines[0]
5226 error_line = lines[first_error]
5227 error_msg = lines[first_error+1:]
5228 error_msg_s = "\n".join(error_msg) + "\n"
5229 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
5231 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
5232 units = [simplejson.loads(line) for line in lines[:first_error]]
5233 self.failUnlessReallyEqual(len(units), 6) # includes subdir
5234 last_unit = units[-1]
5235 self.failUnlessEqual(last_unit["path"], ["subdir"])
5236 r = last_unit["check-results"]["results"]
5237 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
5238 self.failUnlessReallyEqual(r["count-happiness"], 1)
5239 self.failUnlessReallyEqual(r["count-shares-good"], 1)
5240 self.failUnlessReallyEqual(r["recoverable"], False)
5241 d.addCallback(_check_broken_deepcheck)
5243 d.addErrback(self.explain_web_error)
5246 def test_deep_check_and_repair(self):
5247 self.basedir = "web/Grid/deep_check_and_repair"
5249 c0 = self.g.clients[0]
5253 d = c0.create_dirnode()
5254 def _stash_root_and_create_file(n):
5256 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5257 return n.add_file(u"good", upload.Data(DATA, convergence=""))
5258 d.addCallback(_stash_root_and_create_file)
5259 def _stash_uri(fn, which):
5260 self.uris[which] = fn.get_uri()
5261 d.addCallback(_stash_uri, "good")
5262 d.addCallback(lambda ign:
5263 self.rootnode.add_file(u"small",
5264 upload.Data("literal",
5266 d.addCallback(_stash_uri, "small")
5267 d.addCallback(lambda ign:
5268 self.rootnode.add_file(u"sick",
5269 upload.Data(DATA+"1",
5271 d.addCallback(_stash_uri, "sick")
5272 #d.addCallback(lambda ign:
5273 # self.rootnode.add_file(u"dead",
5274 # upload.Data(DATA+"2",
5276 #d.addCallback(_stash_uri, "dead")
5278 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
5279 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
5280 #d.addCallback(_stash_uri, "corrupt")
5282 def _clobber_shares(ignored):
5283 good_shares = self.find_uri_shares(self.uris["good"])
5284 self.failUnlessReallyEqual(len(good_shares), 10)
5285 sick_shares = self.find_uri_shares(self.uris["sick"])
5286 os.unlink(sick_shares[0][2])
5287 #dead_shares = self.find_uri_shares(self.uris["dead"])
5288 #for i in range(1, 10):
5289 # os.unlink(dead_shares[i][2])
5291 #c_shares = self.find_uri_shares(self.uris["corrupt"])
5292 #cso = CorruptShareOptions()
5293 #cso.stdout = StringIO()
5294 #cso.parseOptions([c_shares[0][2]])
5296 d.addCallback(_clobber_shares)
5299 # root/good CHK, 10 shares
5301 # root/sick CHK, 9 shares
5303 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
5305 units = [simplejson.loads(line)
5306 for line in res.splitlines()
5308 self.failUnlessReallyEqual(len(units), 4+1)
5309 # should be parent-first
5311 self.failUnlessEqual(u0["path"], [])
5312 self.failUnlessEqual(u0["type"], "directory")
5313 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
5314 u0crr = u0["check-and-repair-results"]
5315 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
5316 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-happiness"], 10)
5317 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
5319 ugood = [u for u in units
5320 if u["type"] == "file" and u["path"] == [u"good"]][0]
5321 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
5322 ugoodcrr = ugood["check-and-repair-results"]
5323 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
5324 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-happiness"], 10)
5325 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
5327 usick = [u for u in units
5328 if u["type"] == "file" and u["path"] == [u"sick"]][0]
5329 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
5330 usickcrr = usick["check-and-repair-results"]
5331 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
5332 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
5333 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-happiness"], 9)
5334 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
5335 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-happiness"], 10)
5336 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
5339 self.failUnlessEqual(stats["type"], "stats")
5341 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
5342 self.failUnlessReallyEqual(s["count-literal-files"], 1)
5343 self.failUnlessReallyEqual(s["count-directories"], 1)
5344 d.addCallback(_done)
5346 d.addErrback(self.explain_web_error)
5349 def _count_leases(self, ignored, which):
5350 u = self.uris[which]
5351 shares = self.find_uri_shares(u)
5353 for shnum, serverid, fn in shares:
5354 sf = get_share_file(fn)
5355 num_leases = len(list(sf.get_leases()))
5356 lease_counts.append( (fn, num_leases) )
5359 def _assert_leasecount(self, lease_counts, expected):
5360 for (fn, num_leases) in lease_counts:
5361 if num_leases != expected:
5362 self.fail("expected %d leases, have %d, on %s" %
5363 (expected, num_leases, fn))
5365 def test_add_lease(self):
5366 self.basedir = "web/Grid/add_lease"
5367 self.set_up_grid(num_clients=2)
5368 c0 = self.g.clients[0]
5371 d = c0.upload(upload.Data(DATA, convergence=""))
5372 def _stash_uri(ur, which):
5373 self.uris[which] = ur.get_uri()
5374 d.addCallback(_stash_uri, "one")
5375 d.addCallback(lambda ign:
5376 c0.upload(upload.Data(DATA+"1", convergence="")))
5377 d.addCallback(_stash_uri, "two")
5378 def _stash_mutable_uri(n, which):
5379 self.uris[which] = n.get_uri()
5380 assert isinstance(self.uris[which], str)
5381 d.addCallback(lambda ign:
5382 c0.create_mutable_file(publish.MutableData(DATA+"2")))
5383 d.addCallback(_stash_mutable_uri, "mutable")
5385 def _compute_fileurls(ignored):
5387 for which in self.uris:
5388 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
5389 d.addCallback(_compute_fileurls)
5391 d.addCallback(self._count_leases, "one")
5392 d.addCallback(self._assert_leasecount, 1)
5393 d.addCallback(self._count_leases, "two")
5394 d.addCallback(self._assert_leasecount, 1)
5395 d.addCallback(self._count_leases, "mutable")
5396 d.addCallback(self._assert_leasecount, 1)
5398 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
5399 def _got_html_good(res):
5400 self.failUnlessIn("Healthy", res)
5401 self.failIfIn("Not Healthy", res)
5402 d.addCallback(_got_html_good)
5404 d.addCallback(self._count_leases, "one")
5405 d.addCallback(self._assert_leasecount, 1)
5406 d.addCallback(self._count_leases, "two")
5407 d.addCallback(self._assert_leasecount, 1)
5408 d.addCallback(self._count_leases, "mutable")
5409 d.addCallback(self._assert_leasecount, 1)
5411 # this CHECK uses the original client, which uses the same
5412 # lease-secrets, so it will just renew the original lease
5413 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
5414 d.addCallback(_got_html_good)
5416 d.addCallback(self._count_leases, "one")
5417 d.addCallback(self._assert_leasecount, 1)
5418 d.addCallback(self._count_leases, "two")
5419 d.addCallback(self._assert_leasecount, 1)
5420 d.addCallback(self._count_leases, "mutable")
5421 d.addCallback(self._assert_leasecount, 1)
5423 # this CHECK uses an alternate client, which adds a second lease
5424 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
5425 d.addCallback(_got_html_good)
5427 d.addCallback(self._count_leases, "one")
5428 d.addCallback(self._assert_leasecount, 2)
5429 d.addCallback(self._count_leases, "two")
5430 d.addCallback(self._assert_leasecount, 1)
5431 d.addCallback(self._count_leases, "mutable")
5432 d.addCallback(self._assert_leasecount, 1)
5434 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
5435 d.addCallback(_got_html_good)
5437 d.addCallback(self._count_leases, "one")
5438 d.addCallback(self._assert_leasecount, 2)
5439 d.addCallback(self._count_leases, "two")
5440 d.addCallback(self._assert_leasecount, 1)
5441 d.addCallback(self._count_leases, "mutable")
5442 d.addCallback(self._assert_leasecount, 1)
5444 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
5446 d.addCallback(_got_html_good)
5448 d.addCallback(self._count_leases, "one")
5449 d.addCallback(self._assert_leasecount, 2)
5450 d.addCallback(self._count_leases, "two")
5451 d.addCallback(self._assert_leasecount, 1)
5452 d.addCallback(self._count_leases, "mutable")
5453 d.addCallback(self._assert_leasecount, 2)
5455 d.addErrback(self.explain_web_error)
5458 def test_deep_add_lease(self):
5459 self.basedir = "web/Grid/deep_add_lease"
5460 self.set_up_grid(num_clients=2)
5461 c0 = self.g.clients[0]
5465 d = c0.create_dirnode()
5466 def _stash_root_and_create_file(n):
5468 self.uris["root"] = n.get_uri()
5469 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5470 return n.add_file(u"one", upload.Data(DATA, convergence=""))
5471 d.addCallback(_stash_root_and_create_file)
5472 def _stash_uri(fn, which):
5473 self.uris[which] = fn.get_uri()
5474 d.addCallback(_stash_uri, "one")
5475 d.addCallback(lambda ign:
5476 self.rootnode.add_file(u"small",
5477 upload.Data("literal",
5479 d.addCallback(_stash_uri, "small")
5481 d.addCallback(lambda ign:
5482 c0.create_mutable_file(publish.MutableData("mutable")))
5483 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5484 d.addCallback(_stash_uri, "mutable")
5486 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5488 units = [simplejson.loads(line)
5489 for line in res.splitlines()
5491 # root, one, small, mutable, stats
5492 self.failUnlessReallyEqual(len(units), 4+1)
5493 d.addCallback(_done)
5495 d.addCallback(self._count_leases, "root")
5496 d.addCallback(self._assert_leasecount, 1)
5497 d.addCallback(self._count_leases, "one")
5498 d.addCallback(self._assert_leasecount, 1)
5499 d.addCallback(self._count_leases, "mutable")
5500 d.addCallback(self._assert_leasecount, 1)
5502 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5503 d.addCallback(_done)
5505 d.addCallback(self._count_leases, "root")
5506 d.addCallback(self._assert_leasecount, 1)
5507 d.addCallback(self._count_leases, "one")
5508 d.addCallback(self._assert_leasecount, 1)
5509 d.addCallback(self._count_leases, "mutable")
5510 d.addCallback(self._assert_leasecount, 1)
5512 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5514 d.addCallback(_done)
5516 d.addCallback(self._count_leases, "root")
5517 d.addCallback(self._assert_leasecount, 2)
5518 d.addCallback(self._count_leases, "one")
5519 d.addCallback(self._assert_leasecount, 2)
5520 d.addCallback(self._count_leases, "mutable")
5521 d.addCallback(self._assert_leasecount, 2)
5523 d.addErrback(self.explain_web_error)
5527 def test_exceptions(self):
5528 self.basedir = "web/Grid/exceptions"
5529 self.set_up_grid(num_clients=1, num_servers=2)
5530 c0 = self.g.clients[0]
5531 c0.encoding_params['happy'] = 2
5534 d = c0.create_dirnode()
5536 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5537 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5539 d.addCallback(_stash_root)
5540 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5542 self.fileurls["1share"] = "uri/" + urllib.quote(ur.get_uri())
5543 self.delete_shares_numbered(ur.get_uri(), range(1,10))
5545 u = uri.from_string(ur.get_uri())
5546 u.key = testutil.flip_bit(u.key, 0)
5547 baduri = u.to_string()
5548 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5549 d.addCallback(_stash_bad)
5550 d.addCallback(lambda ign: c0.create_dirnode())
5551 def _mangle_dirnode_1share(n):
5553 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5554 self.fileurls["dir-1share-json"] = url + "?t=json"
5555 self.delete_shares_numbered(u, range(1,10))
5556 d.addCallback(_mangle_dirnode_1share)
5557 d.addCallback(lambda ign: c0.create_dirnode())
5558 def _mangle_dirnode_0share(n):
5560 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5561 self.fileurls["dir-0share-json"] = url + "?t=json"
5562 self.delete_shares_numbered(u, range(0,10))
5563 d.addCallback(_mangle_dirnode_0share)
5565 # NotEnoughSharesError should be reported sensibly, with a
5566 # text/plain explanation of the problem, and perhaps some
5567 # information on which shares *could* be found.
5569 d.addCallback(lambda ignored:
5570 self.shouldHTTPError("GET unrecoverable",
5571 410, "Gone", "NoSharesError",
5572 self.GET, self.fileurls["0shares"]))
5573 def _check_zero_shares(body):
5574 self.failIfIn("<html>", body)
5575 body = " ".join(body.strip().split())
5576 exp = ("NoSharesError: no shares could be found. "
5577 "Zero shares usually indicates a corrupt URI, or that "
5578 "no servers were connected, but it might also indicate "
5579 "severe corruption. You should perform a filecheck on "
5580 "this object to learn more. The full error message is: "
5581 "no shares (need 3). Last failure: None")
5582 self.failUnlessReallyEqual(exp, body)
5583 d.addCallback(_check_zero_shares)
5586 d.addCallback(lambda ignored:
5587 self.shouldHTTPError("GET 1share",
5588 410, "Gone", "NotEnoughSharesError",
5589 self.GET, self.fileurls["1share"]))
5590 def _check_one_share(body):
5591 self.failIfIn("<html>", body)
5592 body = " ".join(body.strip().split())
5593 msgbase = ("NotEnoughSharesError: This indicates that some "
5594 "servers were unavailable, or that shares have been "
5595 "lost to server departure, hard drive failure, or disk "
5596 "corruption. You should perform a filecheck on "
5597 "this object to learn more. The full error message is:"
5599 msg1 = msgbase + (" ran out of shares:"
5602 " overdue= unused= need 3. Last failure: None")
5603 msg2 = msgbase + (" ran out of shares:"
5605 " pending=Share(sh0-on-xgru5)"
5606 " overdue= unused= need 3. Last failure: None")
5607 self.failUnless(body == msg1 or body == msg2, body)
5608 d.addCallback(_check_one_share)
5610 d.addCallback(lambda ignored:
5611 self.shouldHTTPError("GET imaginary",
5612 404, "Not Found", None,
5613 self.GET, self.fileurls["imaginary"]))
5614 def _missing_child(body):
5615 self.failUnlessIn("No such child: imaginary", body)
5616 d.addCallback(_missing_child)
5618 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5619 def _check_0shares_dir_html(body):
5620 self.failUnlessIn(DIR_HTML_TAG, body)
5621 # we should see the regular page, but without the child table or
5623 body = " ".join(body.strip().split())
5624 self.failUnlessIn('href="?t=info">More info on this directory',
5626 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5627 "could not be retrieved, because there were insufficient "
5628 "good shares. This might indicate that no servers were "
5629 "connected, insufficient servers were connected, the URI "
5630 "was corrupt, or that shares have been lost due to server "
5631 "departure, hard drive failure, or disk corruption. You "
5632 "should perform a filecheck on this object to learn more.")
5633 self.failUnlessIn(exp, body)
5634 self.failUnlessIn("No upload forms: directory is unreadable", body)
5635 d.addCallback(_check_0shares_dir_html)
5637 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5638 def _check_1shares_dir_html(body):
5639 # at some point, we'll split UnrecoverableFileError into 0-shares
5640 # and some-shares like we did for immutable files (since there
5641 # are different sorts of advice to offer in each case). For now,
5642 # they present the same way.
5643 self.failUnlessIn(DIR_HTML_TAG, body)
5644 body = " ".join(body.strip().split())
5645 self.failUnlessIn('href="?t=info">More info on this directory',
5647 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5648 "could not be retrieved, because there were insufficient "
5649 "good shares. This might indicate that no servers were "
5650 "connected, insufficient servers were connected, the URI "
5651 "was corrupt, or that shares have been lost due to server "
5652 "departure, hard drive failure, or disk corruption. You "
5653 "should perform a filecheck on this object to learn more.")
5654 self.failUnlessIn(exp, body)
5655 self.failUnlessIn("No upload forms: directory is unreadable", body)
5656 d.addCallback(_check_1shares_dir_html)
5658 d.addCallback(lambda ignored:
5659 self.shouldHTTPError("GET dir-0share-json",
5660 410, "Gone", "UnrecoverableFileError",
5662 self.fileurls["dir-0share-json"]))
5663 def _check_unrecoverable_file(body):
5664 self.failIfIn("<html>", body)
5665 body = " ".join(body.strip().split())
5666 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5667 "could not be retrieved, because there were insufficient "
5668 "good shares. This might indicate that no servers were "
5669 "connected, insufficient servers were connected, the URI "
5670 "was corrupt, or that shares have been lost due to server "
5671 "departure, hard drive failure, or disk corruption. You "
5672 "should perform a filecheck on this object to learn more.")
5673 self.failUnlessReallyEqual(exp, body)
5674 d.addCallback(_check_unrecoverable_file)
5676 d.addCallback(lambda ignored:
5677 self.shouldHTTPError("GET dir-1share-json",
5678 410, "Gone", "UnrecoverableFileError",
5680 self.fileurls["dir-1share-json"]))
5681 d.addCallback(_check_unrecoverable_file)
5683 d.addCallback(lambda ignored:
5684 self.shouldHTTPError("GET imaginary",
5685 404, "Not Found", None,
5686 self.GET, self.fileurls["imaginary"]))
5688 # attach a webapi child that throws a random error, to test how it
5690 w = c0.getServiceNamed("webish")
5691 w.root.putChild("ERRORBOOM", ErrorBoom())
5693 # "Accept: */*" : should get a text/html stack trace
5694 # "Accept: text/plain" : should get a text/plain stack trace
5695 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5696 # no Accept header: should get a text/html stack trace
5698 d.addCallback(lambda ignored:
5699 self.shouldHTTPError("GET errorboom_html",
5700 500, "Internal Server Error", None,
5701 self.GET, "ERRORBOOM",
5702 headers={"accept": "*/*"}))
5703 def _internal_error_html1(body):
5704 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5705 d.addCallback(_internal_error_html1)
5707 d.addCallback(lambda ignored:
5708 self.shouldHTTPError("GET errorboom_text",
5709 500, "Internal Server Error", None,
5710 self.GET, "ERRORBOOM",
5711 headers={"accept": "text/plain"}))
5712 def _internal_error_text2(body):
5713 self.failIfIn("<html>", body)
5714 self.failUnless(body.startswith("Traceback "), body)
5715 d.addCallback(_internal_error_text2)
5717 CLI_accepts = "text/plain, application/octet-stream"
5718 d.addCallback(lambda ignored:
5719 self.shouldHTTPError("GET errorboom_text",
5720 500, "Internal Server Error", None,
5721 self.GET, "ERRORBOOM",
5722 headers={"accept": CLI_accepts}))
5723 def _internal_error_text3(body):
5724 self.failIfIn("<html>", body)
5725 self.failUnless(body.startswith("Traceback "), body)
5726 d.addCallback(_internal_error_text3)
5728 d.addCallback(lambda ignored:
5729 self.shouldHTTPError("GET errorboom_text",
5730 500, "Internal Server Error", None,
5731 self.GET, "ERRORBOOM"))
5732 def _internal_error_html4(body):
5733 self.failUnlessIn("<html>", body)
5734 d.addCallback(_internal_error_html4)
5736 def _flush_errors(res):
5737 # Trial: please ignore the CompletelyUnhandledError in the logs
5738 self.flushLoggedErrors(CompletelyUnhandledError)
5740 d.addBoth(_flush_errors)
5744 def test_blacklist(self):
5745 # download from a blacklisted URI, get an error
5746 self.basedir = "web/Grid/blacklist"
5748 c0 = self.g.clients[0]
5749 c0_basedir = c0.basedir
5750 fn = os.path.join(c0_basedir, "access.blacklist")
5752 DATA = "off-limits " * 50
5754 d = c0.upload(upload.Data(DATA, convergence=""))
5755 def _stash_uri_and_create_dir(ur):
5756 self.uri = ur.get_uri()
5757 self.url = "uri/"+self.uri
5758 u = uri.from_string_filenode(self.uri)
5759 self.si = u.get_storage_index()
5760 childnode = c0.create_node_from_uri(self.uri, None)
5761 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5762 d.addCallback(_stash_uri_and_create_dir)
5763 def _stash_dir(node):
5764 self.dir_node = node
5765 self.dir_uri = node.get_uri()
5766 self.dir_url = "uri/"+self.dir_uri
5767 d.addCallback(_stash_dir)
5768 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5769 def _check_dir_html(body):
5770 self.failUnlessIn(DIR_HTML_TAG, body)
5771 self.failUnlessIn("blacklisted.txt</a>", body)
5772 d.addCallback(_check_dir_html)
5773 d.addCallback(lambda ign: self.GET(self.url))
5774 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5776 def _blacklist(ign):
5778 f.write(" # this is a comment\n")
5780 f.write("\n") # also exercise blank lines
5781 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5783 # clients should be checking the blacklist each time, so we don't
5784 # need to restart the client
5785 d.addCallback(_blacklist)
5786 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5788 "Access Prohibited: off-limits",
5789 self.GET, self.url))
5791 # We should still be able to list the parent directory, in HTML...
5792 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5793 def _check_dir_html2(body):
5794 self.failUnlessIn(DIR_HTML_TAG, body)
5795 self.failUnlessIn("blacklisted.txt</strike>", body)
5796 d.addCallback(_check_dir_html2)
5798 # ... and in JSON (used by CLI).
5799 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5800 def _check_dir_json(res):
5801 data = simplejson.loads(res)
5802 self.failUnless(isinstance(data, list), data)
5803 self.failUnlessEqual(data[0], "dirnode")
5804 self.failUnless(isinstance(data[1], dict), data)
5805 self.failUnlessIn("children", data[1])
5806 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5807 childdata = data[1]["children"]["blacklisted.txt"]
5808 self.failUnless(isinstance(childdata, list), data)
5809 self.failUnlessEqual(childdata[0], "filenode")
5810 self.failUnless(isinstance(childdata[1], dict), data)
5811 d.addCallback(_check_dir_json)
5813 def _unblacklist(ign):
5814 open(fn, "w").close()
5815 # the Blacklist object watches mtime to tell when the file has
5816 # changed, but on windows this test will run faster than the
5817 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5818 # to force a reload.
5819 self.g.clients[0].blacklist.last_mtime -= 2.0
5820 d.addCallback(_unblacklist)
5822 # now a read should work
5823 d.addCallback(lambda ign: self.GET(self.url))
5824 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5826 # read again to exercise the blacklist-is-unchanged logic
5827 d.addCallback(lambda ign: self.GET(self.url))
5828 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5830 # now add a blacklisted directory, and make sure files under it are
5833 childnode = c0.create_node_from_uri(self.uri, None)
5834 return c0.create_dirnode({u"child": (childnode,{}) })
5835 d.addCallback(_add_dir)
5836 def _get_dircap(dn):
5837 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5838 self.dir_url_base = "uri/"+dn.get_write_uri()
5839 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5840 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5841 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5842 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5843 d.addCallback(_get_dircap)
5844 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5845 d.addCallback(lambda body: self.failUnlessIn(DIR_HTML_TAG, body))
5846 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5847 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5848 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5849 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5850 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5851 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5852 d.addCallback(lambda ign: self.GET(self.child_url))
5853 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5855 def _block_dir(ign):
5857 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5859 self.g.clients[0].blacklist.last_mtime -= 2.0
5860 d.addCallback(_block_dir)
5861 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5863 "Access Prohibited: dir-off-limits",
5864 self.GET, self.dir_url_base))
5865 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5867 "Access Prohibited: dir-off-limits",
5868 self.GET, self.dir_url_json1))
5869 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5871 "Access Prohibited: dir-off-limits",
5872 self.GET, self.dir_url_json2))
5873 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5875 "Access Prohibited: dir-off-limits",
5876 self.GET, self.dir_url_json_ro))
5877 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5879 "Access Prohibited: dir-off-limits",
5880 self.GET, self.child_url))
5884 class CompletelyUnhandledError(Exception):
5886 class ErrorBoom(rend.Page):
5887 def beforeRender(self, ctx):
5888 raise CompletelyUnhandledError("whoops")