Ticket #637: ticket637.darcspatch.txt

File ticket637.darcspatch.txt, 34.4 KB (added by davidsarah, at 2009-11-21T06:02:13Z)

storage server: detect disk space usage on Windows too (fixes #637)

Line 
1Sat Nov 21 05:56:44 GMT Standard Time 2009  david-sarah@jacaranda.org
2  * storage server: detect disk space usage on Windows too (fixes #637)
3
4New patches:
5
6[storage server: detect disk space usage on Windows too (fixes #637)
7david-sarah@jacaranda.org**20091121055644
8 Ignore-this: 20fb30498174ce997befac7701fab056
9] {
10hunk ./_auto_deps.py 40
11     install_requires.append("pysqlite >= 2.0.5")
12 
13 ## The following block is commented-out because there is not currently a pywin32 package which
14-## can be easy_install'ed and also which actually makes "import win32api" succeed.  Users have
15-## to manually install pywin32 on Windows before installing Tahoe.
16+## can be easy_install'ed and also which actually makes "import win32api" succeed.
17+## See http://sourceforge.net/tracker/index.php?func=detail&aid=1799934&group_id=78018&atid=551954
18+## Users have to manually install pywin32 on Windows before installing Tahoe.
19 ##import platform
20 ##if platform.system() == "Windows":
21 ##    # Twisted requires pywin32 if it is going to offer process management functionality, or if
22hunk ./_auto_deps.py 48
23 ##    # it is going to offer iocp reactor.  We currently require process management.  It would be
24 ##    # better if Twisted would declare that it requires pywin32 if it is going to offer process
25-##    # management.  Then the specification and the evolution of Twisted's reliance on pywin32 can
26-##    # be confined to the Twisted setup data, and Tahoe can remain blissfully ignorant about such
27-##    # things as if a future version of Twisted requires a different version of pywin32, or if a
28-##    # future version of Twisted implements process management without using pywin32 at all,
29-##    # etc..  That is twisted ticket #3238 -- http://twistedmatrix.com/trac/ticket/3238 .  But
30-##    # until Twisted does that, Tahoe needs to be non-ignorant of the following requirement:
31+##    # management.  That is twisted ticket #3238 -- http://twistedmatrix.com/trac/ticket/3238 .
32+##    # On the other hand, Tahoe also depends on pywin32 for getting free disk space statistics
33+##    # (although that is not a hard requirement: if win32api can't be imported then we don't
34+##    # rely on having the disk stats).
35 ##    install_requires.append('pywin32')
36 
37 if hasattr(sys, 'frozen'): # for py2exe
38hunk ./docs/configuration.txt 305
39 reserved_space = (str, optional)
40 
41  If provided, this value defines how much disk space is reserved: the storage
42- server will not accept any share which causes the amount of free space (as
43- measured by 'df', or more specifically statvfs(2)) to drop below this value.
44+ server will not accept any share which causes the amount of free disk space
45+ to drop below this value. (The free space is measured by a call to statvfs(2)
46+ on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
47+ user account under which the storage server runs.)
48 
49  This string contains a number, with an optional case-insensitive scale
50  suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
51hunk ./src/allmydata/storage/server.py 39
52     implements(RIStorageServer, IStatsProducer)
53     name = 'storage'
54     LeaseCheckerClass = LeaseCheckingCrawler
55+    windows = False
56+
57+    try:
58+        import win32api, win32con
59+        windows = True
60+        # <http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx>
61+        win32api.SetErrorMode(win32con.SEM_FAILCRITICALERRORS |
62+                              win32con.SEM_NOOPENFILEERRORBOX)
63+    except ImportError:
64+        pass
65 
66     def __init__(self, storedir, nodeid, reserved_space=0,
67                  discard_storage=False, readonly_storage=False,
68hunk ./src/allmydata/storage/server.py 83
69 
70         if reserved_space:
71             if self.get_available_space() is None:
72-                log.msg("warning: [storage]reserved_space= is set, but this platform does not support statvfs(2), so this reservation cannot be honored",
73+                log.msg("warning: [storage]reserved_space= is set, but this platform does not support an API to get disk statistics (statvfs(2) or GetDiskFreeSpaceEx), so this reservation cannot be honored",
74                         umin="0wZ27w", level=log.UNUSUAL)
75 
76         self.latencies = {"allocate": [], # immutable
77hunk ./src/allmydata/storage/server.py 160
78     def _clean_incomplete(self):
79         fileutil.rm_dir(self.incomingdir)
80 
81-    def do_statvfs(self):
82-        return os.statvfs(self.storedir)
83+    def get_disk_stats(self):
84+        """Return disk statistics for the storage disk, in the form of a dict
85+        with the following fields.
86+          total:            total bytes on disk
87+          free_for_root:    bytes actually free on disk
88+          free_for_nonroot: bytes free for "a non-privileged user" [Unix] or
89+                              the current user [Windows]; might take into
90+                              account quotas depending on platform
91+          used:             bytes used on disk
92+          avail:            bytes available excluding reserved space
93+        An AttributeError can occur if the OS has no API to get disk information.
94+        An EnvironmentError can occur if the OS call fails."""
95+
96+        if self.windows:
97+            # For Windows systems, where os.statvfs is not available, use GetDiskFreeSpaceEx.
98+            # <http://docs.activestate.com/activepython/2.5/pywin32/win32api__GetDiskFreeSpaceEx_meth.html>
99+            #
100+            # Although the docs say that the argument should be the root directory
101+            # of a disk, GetDiskFreeSpaceEx actually accepts any path on that disk
102+            # (like its Win32 equivalent).
103+
104+            (free_for_nonroot, total, free_for_root) = self.win32api.GetDiskFreeSpaceEx(self.storedir)
105+        else:
106+            # For Unix-like systems.
107+            # <http://docs.python.org/library/os.html#os.statvfs>
108+            # <http://opengroup.org/onlinepubs/7990989799/xsh/fstatvfs.html>
109+            # <http://opengroup.org/onlinepubs/7990989799/xsh/sysstatvfs.h.html>
110+            s = os.statvfs(self.storedir)
111 
112hunk ./src/allmydata/storage/server.py 189
113-    def get_stats(self):
114-        # remember: RIStatsProvider requires that our return dict
115-        # contains numeric values.
116-        stats = { 'storage_server.allocated': self.allocated_size(), }
117-        stats["storage_server.reserved_space"] = self.reserved_space
118-        for category,ld in self.get_latencies().items():
119-            for name,v in ld.items():
120-                stats['storage_server.latencies.%s.%s' % (category, name)] = v
121-        writeable = True
122-        if self.readonly_storage:
123-            writeable = False
124-        try:
125-            s = self.do_statvfs()
126             # on my mac laptop:
127             #  statvfs(2) is a wrapper around statfs(2).
128             #    statvfs.f_frsize = statfs.f_bsize :
129hunk ./src/allmydata/storage/server.py 199
130             # wrong, and s.f_blocks*s.f_frsize is twice the size of my disk,
131             # but s.f_bavail*s.f_frsize is correct
132 
133-            disk_total = s.f_frsize * s.f_blocks
134-            disk_used = s.f_frsize * (s.f_blocks - s.f_bfree)
135-            # spacetime predictors should look at the slope of disk_used.
136-            disk_free_for_root = s.f_frsize * s.f_bfree
137-            disk_free_for_nonroot = s.f_frsize * s.f_bavail
138+            total = s.f_frsize * s.f_blocks
139+            free_for_root = s.f_frsize * s.f_bfree
140+            free_for_nonroot = s.f_frsize * s.f_bavail
141+
142+        # valid for all platforms:
143+        used = total - free_for_root
144+        avail = max(free_for_nonroot - self.reserved_space, 0)
145 
146hunk ./src/allmydata/storage/server.py 207
147-            # include our local policy here: if we stop accepting shares when
148-            # the available space drops below 1GB, then include that fact in
149-            # disk_avail.
150-            disk_avail = disk_free_for_nonroot - self.reserved_space
151-            disk_avail = max(disk_avail, 0)
152-            if self.readonly_storage:
153-                disk_avail = 0
154-            if disk_avail == 0:
155-                writeable = False
156+        return { 'total': total, 'free_for_root': free_for_root,
157+                 'free_for_nonroot': free_for_nonroot,
158+                 'used': used, 'avail': avail, }
159+
160+    def get_stats(self):
161+        # remember: RIStatsProvider requires that our return dict
162+        # contains numeric values.
163+        stats = { 'storage_server.allocated': self.allocated_size(), }
164+        stats['storage_server.reserved_space'] = self.reserved_space
165+        for category,ld in self.get_latencies().items():
166+            for name,v in ld.items():
167+                stats['storage_server.latencies.%s.%s' % (category, name)] = v
168+
169+        try:
170+            disk = self.get_disk_stats()
171+            writeable = disk['avail'] > 0
172 
173             # spacetime predictors should use disk_avail / (d(disk_used)/dt)
174hunk ./src/allmydata/storage/server.py 225
175-            stats["storage_server.disk_total"] = disk_total
176-            stats["storage_server.disk_used"] = disk_used
177-            stats["storage_server.disk_free_for_root"] = disk_free_for_root
178-            stats["storage_server.disk_free_for_nonroot"] = disk_free_for_nonroot
179-            stats["storage_server.disk_avail"] = disk_avail
180+            stats['storage_server.disk_total'] = disk['total']
181+            stats['storage_server.disk_used'] = disk['used']
182+            stats['storage_server.disk_free_for_root'] = disk['free_for_root']
183+            stats['storage_server.disk_free_for_nonroot'] = disk['free_for_nonroot']
184+            stats['storage_server.disk_avail'] = disk['avail']
185         except AttributeError:
186hunk ./src/allmydata/storage/server.py 231
187-            # os.statvfs is available only on unix
188-            pass
189-        stats["storage_server.accepting_immutable_shares"] = int(writeable)
190+            writeable = True
191+        except EnvironmentError:
192+            log.msg("OS call to get disk statistics failed", level=log.UNUSUAL)
193+            writeable = False
194+
195+        if self.readonly_storage:
196+            stats['storage_server.disk_avail'] = 0
197+            writeable = False
198+
199+        stats['storage_server.accepting_immutable_shares'] = int(writeable)
200         s = self.bucket_counter.get_state()
201         bucket_count = s.get("last-complete-bucket-count")
202         if bucket_count:
203hunk ./src/allmydata/storage/server.py 244
204-            stats["storage_server.total_bucket_count"] = bucket_count
205+            stats['storage_server.total_bucket_count'] = bucket_count
206         return stats
207 
208hunk ./src/allmydata/storage/server.py 247
209-
210-    def stat_disk(self, d):
211-        s = os.statvfs(d)
212-        # s.f_bavail: available to non-root users
213-        disk_avail = s.f_frsize * s.f_bavail
214-        return disk_avail
215-
216     def get_available_space(self):
217hunk ./src/allmydata/storage/server.py 248
218-        # returns None if it cannot be measured (windows)
219+        """Returns available space for share storage in bytes, or None if no
220+        API to get this information is available."""
221+
222+        if self.readonly_storage:
223+            return 0
224         try:
225hunk ./src/allmydata/storage/server.py 254
226-            disk_avail = self.stat_disk(self.storedir)
227-            disk_avail -= self.reserved_space
228+            return self.get_disk_stats()['avail']
229         except AttributeError:
230hunk ./src/allmydata/storage/server.py 256
231-            disk_avail = None
232-        if self.readonly_storage:
233-            disk_avail = 0
234-        return disk_avail
235+            return None
236+        except EnvironmentError:
237+            log.msg("OS call to get disk statistics failed", level=log.UNUSUAL)
238+            return 0
239 
240     def allocated_size(self):
241         space = 0
242hunk ./src/allmydata/storage/server.py 270
243     def remote_get_version(self):
244         remaining_space = self.get_available_space()
245         if remaining_space is None:
246-            # we're on a platform that doesn't have 'df', so make a vague
247-            # guess.
248+            # We're on a platform that has no API to get disk stats.
249             remaining_space = 2**64
250hunk ./src/allmydata/storage/server.py 272
251+
252         version = { "http://allmydata.org/tahoe/protocols/storage/v1" :
253                     { "maximum-immutable-share-size": remaining_space,
254                       "tolerates-immutable-read-overrun": True,
255hunk ./src/allmydata/storage/server.py 326
256             sf = ShareFile(fn)
257             sf.add_or_renew_lease(lease_info)
258 
259-        # self.readonly_storage causes remaining_space=0
260+        # self.readonly_storage causes remaining_space <= 0
261 
262         for shnum in sharenums:
263             incominghome = os.path.join(self.incomingdir, si_dir, "%d" % shnum)
264hunk ./src/allmydata/test/test_storage.py 231
265                                        0x44, WriteBucketProxy_v2, ReadBucketProxy)
266 
267 class FakeDiskStorageServer(StorageServer):
268-    def stat_disk(self, d):
269-        return self.DISKAVAIL
270+    DISKAVAIL = 0
271+    def get_disk_stats(self):
272+        return { 'free_for_nonroot': self.DISKAVAIL, 'avail': max(self.DISKAVAIL - self.reserved_space, 0), }
273 
274 class Server(unittest.TestCase):
275 
276hunk ./src/allmydata/test/test_storage.py 416
277     def test_reserved_space(self):
278         ss = self.create("test_reserved_space", reserved_space=10000,
279                          klass=FakeDiskStorageServer)
280-        # the FakeDiskStorageServer doesn't do real statvfs() calls
281+        # the FakeDiskStorageServer doesn't do real calls to get_disk_stats
282         ss.DISKAVAIL = 15000
283         # 15k available, 10k reserved, leaves 5k for shares
284 
285hunk ./src/allmydata/test/test_storage.py 472
286         ss.disownServiceParent()
287         del ss
288 
289+    def test_disk_stats(self):
290+        # This will spuriously fail if there is zero disk space left (but so will other tests).
291+        ss = self.create("test_disk_stats", reserved_space=0)
292+
293+        disk = ss.get_disk_stats()
294+        self.failUnless(disk['total'] > 0, disk['total'])
295+        self.failUnless(disk['used'] > 0, disk['used'])
296+        self.failUnless(disk['free_for_root'] > 0, disk['free_for_root'])
297+        self.failUnless(disk['free_for_nonroot'] > 0, disk['free_for_nonroot'])
298+        self.failUnless(disk['avail'] > 0, disk['avail'])
299+
300+    def test_disk_stats_avail_nonnegative(self):
301+        ss = self.create("test_disk_stats_avail_nonnegative", reserved_space=2**64)
302+
303+        disk = ss.get_disk_stats()
304+        self.failUnlessEqual(disk['avail'], 0)
305+
306     def test_seek(self):
307         basedir = self.workdir("test_seek_behavior")
308         fileutil.make_dirs(basedir)
309hunk ./src/allmydata/test/test_storage.py 645
310         self.failUnlessEqual(writers, {})
311 
312         stats = ss.get_stats()
313-        self.failUnlessEqual(stats["storage_server.accepting_immutable_shares"],
314-                             False)
315+        self.failUnlessEqual(stats["storage_server.accepting_immutable_shares"], 0)
316         if "storage_server.disk_avail" in stats:
317hunk ./src/allmydata/test/test_storage.py 647
318-            # windows does not have os.statvfs, so it doesn't give us disk
319-            # stats. But if there are stats, readonly_storage means
320-            # disk_avail=0
321+            # Some platforms may not have an API to get disk stats.
322+            # But if there are stats, readonly_storage means disk_avail=0
323             self.failUnlessEqual(stats["storage_server.disk_avail"], 0)
324 
325     def test_discard(self):
326hunk ./src/allmydata/test/test_storage.py 2424
327         d = self.render1(page, args={"t": ["json"]})
328         return d
329 
330-class NoStatvfsServer(StorageServer):
331-    def do_statvfs(self):
332+class NoDiskStatsServer(StorageServer):
333+    def get_disk_stats(self):
334         raise AttributeError
335 
336hunk ./src/allmydata/test/test_storage.py 2428
337+class BadDiskStatsServer(StorageServer):
338+    def get_disk_stats(self):
339+        raise OSError
340+
341 class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin):
342 
343     def setUp(self):
344hunk ./src/allmydata/test/test_storage.py 2473
345         d = self.render1(page, args={"t": ["json"]})
346         return d
347 
348-    def test_status_no_statvfs(self):
349-        # windows has no os.statvfs . Make sure the code handles that even on
350-        # unix.
351-        basedir = "storage/WebStatus/status_no_statvfs"
352+    def test_status_no_disk_stats(self):
353+        # Some platforms may have no disk stats API. Make sure the code can handle that
354+        # (test runs on all platforms).
355+        basedir = "storage/WebStatus/status_no_disk_stats"
356         fileutil.make_dirs(basedir)
357hunk ./src/allmydata/test/test_storage.py 2478
358-        ss = NoStatvfsServer(basedir, "\x00" * 20)
359+        ss = NoDiskStatsServer(basedir, "\x00" * 20)
360         ss.setServiceParent(self.s)
361         w = StorageStatus(ss)
362         html = w.renderSynchronously()
363hunk ./src/allmydata/test/test_storage.py 2486
364         s = remove_tags(html)
365         self.failUnless("Accepting new shares: Yes" in s, s)
366         self.failUnless("Total disk space: ?" in s, s)
367+        self.failUnless("Space Available to Tahoe: ?" in s, s)
368+        self.failUnless(ss.get_available_space() is None)
369+
370+    def test_status_bad_disk_stats(self):
371+        # If the API to get disk stats exists but a call to it fails, then the status should
372+        # show that no shares will be accepted, and get_available_space() should be 0.
373+        basedir = "storage/WebStatus/status_bad_disk_stats"
374+        fileutil.make_dirs(basedir)
375+        ss = BadDiskStatsServer(basedir, "\x00" * 20)
376+        ss.setServiceParent(self.s)
377+        w = StorageStatus(ss)
378+        html = w.renderSynchronously()
379+        self.failUnless("<h1>Storage Server Status</h1>" in html, html)
380+        s = remove_tags(html)
381+        self.failUnless("Accepting new shares: No" in s, s)
382+        self.failUnless("Total disk space: ?" in s, s)
383+        self.failUnless("Space Available to Tahoe: ?" in s, s)
384+        self.failUnless(ss.get_available_space() == 0)
385 
386     def test_readonly(self):
387         basedir = "storage/WebStatus/readonly"
388}
389
390Context:
391
392[webapi: use t=mkdir-with-children instead of a children= arg to t=mkdir .
393Brian Warner <warner@lothar.com>**20091026011321
394 Ignore-this: 769cab30b6ab50db95000b6c5a524916
395 
396 This is safer: in the earlier API, an old webapi server would silently ignore
397 the initial children, and clients trying to set them would have to fetch the
398 newly-created directory to discover the incompatibility. In the new API,
399 clients using t=mkdir-with-children against an old webapi server will get a
400 clear error.
401]
402[nodemaker.create_new_mutable_directory: pack_children() in initial_contents=
403Brian Warner <warner@lothar.com>**20091020005118
404 Ignore-this: bd43c4eefe06fd32b7492bcb0a55d07e
405 instead of creating an empty file and then adding the children later.
406 
407 This should speed up mkdir(initial_children) considerably, removing two
408 roundtrips and an entire read-modify-write cycle, probably bringing it down
409 to a single roundtrip. A quick test (against the volunteergrid) suggests a
410 30% speedup.
411 
412 test_dirnode: add new tests to enforce the restrictions that interfaces.py
413 claims for create_new_mutable_directory(): no UnknownNodes, metadata dicts
414]
415[test_dirnode.py: add tests of initial_children= args to client.create_dirnode
416Brian Warner <warner@lothar.com>**20091017194159
417 Ignore-this: 2e2da28323a4d5d815466387914abc1b
418 and nodemaker.create_new_mutable_directory
419]
420[update many dirnode interfaces to accept dict-of-nodes instead of dict-of-caps
421Brian Warner <warner@lothar.com>**20091017192829
422 Ignore-this: b35472285143862a856bf4b361d692f0
423 
424 interfaces.py: define INodeMaker, document argument values, change
425                create_new_mutable_directory() to take dict-of-nodes. Change
426                dirnode.set_nodes() and dirnode.create_subdirectory() too.
427 nodemaker.py: use INodeMaker, update create_new_mutable_directory()
428 client.py: have create_dirnode() delegate initial_children= to nodemaker
429 dirnode.py (Adder): take dict-of-nodes instead of list-of-nodes, which
430                     updates set_nodes() and create_subdirectory()
431 web/common.py (convert_initial_children_json): create dict-of-nodes
432 web/directory.py: same
433 web/unlinked.py: same
434 test_dirnode.py: update tests to match
435]
436[dirnode.py: move pack_children() out to a function, for eventual use by others
437Brian Warner <warner@lothar.com>**20091017180707
438 Ignore-this: 6a823fb61f2c180fd38d6742d3196a7a
439]
440[move dirnode.CachingDict to dictutil.AuxValueDict, generalize method names,
441Brian Warner <warner@lothar.com>**20091017180005
442 Ignore-this: b086933cf429df0fcea16a308d2640dd
443 improve tests. Let dirnode _pack_children accept either dict or AuxValueDict.
444]
445[test/common.py: update FakeMutableFileNode to new contents= callable scheme
446Brian Warner <warner@lothar.com>**20091013052154
447 Ignore-this: 62f00a76454a2190d1c8641c5993632f
448]
449[The initial_children= argument to nodemaker.create_new_mutable_directory is
450Brian Warner <warner@lothar.com>**20091013031922
451 Ignore-this: 72e45317c21f9eb9ec3bd79bd4311f48
452 now enabled.
453]
454[client.create_mutable_file(contents=) now accepts a callable, which is
455Brian Warner <warner@lothar.com>**20091013031232
456 Ignore-this: 3c89d2f50c1e652b83f20bd3f4f27c4b
457 invoked with the new MutableFileNode and is supposed to return the initial
458 contents. This can be used by e.g. a new dirnode which needs the filenode's
459 writekey to encrypt its initial children.
460 
461 create_mutable_file() still accepts a bytestring too, or None for an empty
462 file.
463]
464[webapi: t=mkdir now accepts initial children, using the same JSON that t=json
465Brian Warner <warner@lothar.com>**20091013023444
466 Ignore-this: 574a46ed46af4251abf8c9580fd31ef7
467 emits.
468 
469 client.create_dirnode(initial_children=) now works.
470]
471[replace dirnode.create_empty_directory() with create_subdirectory(), which
472Brian Warner <warner@lothar.com>**20091013021520
473 Ignore-this: 6b57cb51bcfcc6058d0df569fdc8a9cf
474 takes an initial_children= argument
475]
476[dirnode.set_children: change return value: fire with self instead of None
477Brian Warner <warner@lothar.com>**20091013015026
478 Ignore-this: f1d14e67e084e4b2a4e25fa849b0e753
479]
480[dirnode.set_nodes: change return value: fire with self instead of None
481Brian Warner <warner@lothar.com>**20091013014546
482 Ignore-this: b75b3829fb53f7399693f1c1a39aacae
483]
484[dirnode.set_children: take a dict, not a list
485Brian Warner <warner@lothar.com>**20091013002440
486 Ignore-this: 540ce72ce2727ee053afaae1ff124e21
487]
488[dirnode.set_uri/set_children: change signature to take writecap+readcap
489Brian Warner <warner@lothar.com>**20091012235126
490 Ignore-this: 5df617b2d379a51c79148a857e6026b1
491 instead of a single cap. The webapi t=set_children call benefits too.
492]
493[replace Client.create_empty_dirnode() with create_dirnode(), in anticipation
494Brian Warner <warner@lothar.com>**20091012224506
495 Ignore-this: cbdaa4266ecb3c6496ffceab4f95709d
496 of adding initial_children= argument.
497 
498 Includes stubbed-out initial_children= support.
499]
500[test_web.py: use a less-fake client, making test harness smaller
501Brian Warner <warner@lothar.com>**20091012222808
502 Ignore-this: 29e95147f8c94282885c65b411d100bb
503]
504[webapi.txt: document t=set_children, other small edits
505Brian Warner <warner@lothar.com>**20091009200446
506 Ignore-this: 4d7e76b04a7b8eaa0a981879f778ea5d
507]
508[Verifier: check the full cryptext-hash tree on each share. Removed .todos
509Brian Warner <warner@lothar.com>**20091005221849
510 Ignore-this: 6fb039c5584812017d91725e687323a5
511 from the last few test_repairer tests that were waiting on this.
512]
513[Verifier: check the full block-hash-tree on each share
514Brian Warner <warner@lothar.com>**20091005214844
515 Ignore-this: 3f7ccf6d253f32340f1bf1da27803eee
516 
517 Removed the .todo from two test_repairer tests that check this. The only
518 remaining .todos are on the three crypttext-hash-tree tests.
519]
520[Verifier: check the full share-hash chain on each share
521Brian Warner <warner@lothar.com>**20091005213443
522 Ignore-this: 3d30111904158bec06a4eac22fd39d17
523 
524 Removed the .todo from two test_repairer tests that check this.
525]
526[test_repairer: rename Verifier test cases to be more precise and less verbose
527Brian Warner <warner@lothar.com>**20091005201115
528 Ignore-this: 64be7094e33338c7c2aea9387e138771
529]
530[immutable/checker.py: rearrange code a little bit, make it easier to follow
531Brian Warner <warner@lothar.com>**20091005200252
532 Ignore-this: 91cc303fab66faf717433a709f785fb5
533]
534[test/common.py: wrap docstrings to 80cols so I can read them more easily
535Brian Warner <warner@lothar.com>**20091005200143
536 Ignore-this: b180a3a0235cbe309c87bd5e873cbbb3
537]
538[immutable/download.py: wrap to 80cols, no functional changes
539Brian Warner <warner@lothar.com>**20091005192542
540 Ignore-this: 6b05fe3dc6d78832323e708b9e6a1fe
541]
542[CHK-hashes.svg: cross out plaintext hashes, since we don't include
543Brian Warner <warner@lothar.com>**20091005010803
544 Ignore-this: bea2e953b65ec7359363aa20de8cb603
545 them (until we finish #453)
546]
547[docs: a few licensing clarifications requested by Ubuntu
548zooko@zooko.com**20090927033226
549 Ignore-this: 749fc8c9aeb6dc643669854a3e81baa7
550]
551[setup: remove binary WinFUSE modules
552zooko@zooko.com**20090924211436
553 Ignore-this: 8aefc571d2ae22b9405fc650f2c2062
554 I would prefer to have just source code, or indications of what 3rd-party packages are required, under revision control, and have the build process generate o
555 r acquire the binaries as needed.  Also, having these in our release tarballs is interfering with getting Tahoe-LAFS uploaded into Ubuntu Karmic.  (Technicall
556 y, they would accept binary modules as long as they came with the accompanying source so that they could satisfy their obligations under GPL2+ and TGPPL1+, bu
557 t it is easier for now to remove the binaries from the source tree.)
558 In this case, the binaries are from the tahoe-w32-client project: http://allmydata.org/trac/tahoe-w32-client , from which you can also get the source.
559]
560[setup: remove binary _fusemodule.so 's
561zooko@zooko.com**20090924211130
562 Ignore-this: 74487bbe27d280762ac5dd5f51e24186
563 I would prefer to have just source code, or indications of what 3rd-party packages are required, under revision control, and have the build process generate or acquire the binaries as needed.  Also, having these in our release tarballs is interfering with getting Tahoe-LAFS uploaded into Ubuntu Karmic.  (Technically, they would accept binary modules as long as they came with the accompanying source so that they could satisfy their obligations under GPL2+ and TGPPL1+, but it is easier for now to remove the binaries from the source tree.)
564 In this case, these modules come from the MacFUSE project: http://code.google.com/p/macfuse/
565]
566[doc: add a copy of LGPL2 for documentation purposes for ubuntu
567zooko@zooko.com**20090924054218
568 Ignore-this: 6a073b48678a7c84dc4fbcef9292ab5b
569]
570[setup: remove a convenience copy of figleaf, to ease inclusion into Ubuntu Karmic Koala
571zooko@zooko.com**20090924053215
572 Ignore-this: a0b0c990d6e2ee65c53a24391365ac8d
573 We need to carefully document the licence of figleaf in order to get Tahoe-LAFS into Ubuntu Karmic Koala.  However, figleaf isn't really a part of Tahoe-LAFS per se -- this is just a "convenience copy" of a development tool.  The quickest way to make Tahoe-LAFS acceptable for Karmic then, is to remove figleaf from the Tahoe-LAFS tarball itself.  People who want to run figleaf on Tahoe-LAFS (as everyone should want) can install figleaf themselves.  I haven't tested this -- there may be incompatibilities between upstream figleaf and the copy that we had here...
574]
575[setup: shebang for misc/build-deb.py to fail quickly
576zooko@zooko.com**20090819135626
577 Ignore-this: 5a1b893234d2d0bb7b7346e84b0a6b4d
578 Without this patch, when I ran "chmod +x ./misc/build-deb.py && ./misc/build-deb.py" then it hung indefinitely.  (I wonder what it was doing.)
579]
580[docs: Shawn Willden grants permission for his contributions under GPL2+|TGPPL1+
581zooko@zooko.com**20090921164651
582 Ignore-this: ef1912010d07ff2ffd9678e7abfd0d57
583]
584[docs: Csaba Henk granted permission to license fuse.py under the same terms as Tahoe-LAFS itself
585zooko@zooko.com**20090921154659
586 Ignore-this: c61ba48dcb7206a89a57ca18a0450c53
587]
588[setup: mark setup.py as having utf-8 encoding in it
589zooko@zooko.com**20090920180343
590 Ignore-this: 9d3850733700a44ba7291e9c5e36bb91
591]
592[doc: licensing cleanups
593zooko@zooko.com**20090920171631
594 Ignore-this: 7654f2854bf3c13e6f4d4597633a6630
595 Use nice utf-8 © instead of "(c)". Remove licensing statements on utility modules that have been assigned to allmydata.com by their original authors. (Nattraverso was not assigned to allmydata.com -- it was LGPL'ed -- but I checked and src/allmydata/util/iputil.py was completely rewritten and doesn't contain any line of code from nattraverso.)  Add notes to misc/debian/copyright about licensing on files that aren't just allmydata.com-licensed.
596]
597[build-deb.py: run darcsver early, otherwise we get the wrong version later on
598Brian Warner <warner@lothar.com>**20090918033620
599 Ignore-this: 6635c5b85e84f8aed0d8390490c5392a
600]
601[new approach for debian packaging, sharing pieces across distributions. Still experimental, still only works for sid.
602warner@lothar.com**20090818190527
603 Ignore-this: a75eb63db9106b3269badbfcdd7f5ce1
604]
605[new experimental deb-packaging rules. Only works for sid so far.
606Brian Warner <warner@lothar.com>**20090818014052
607 Ignore-this: 3a26ad188668098f8f3cc10a7c0c2f27
608]
609[setup.py: read _version.py and pass to setup(version=), so more commands work
610Brian Warner <warner@lothar.com>**20090818010057
611 Ignore-this: b290eb50216938e19f72db211f82147e
612 like "setup.py --version" and "setup.py --fullname"
613]
614[test/check_speed.py: fix shbang line
615Brian Warner <warner@lothar.com>**20090818005948
616 Ignore-this: 7f3a37caf349c4c4de704d0feb561f8d
617]
618[setup: remove bundled version of darcsver-1.2.1
619zooko@zooko.com**20090816233432
620 Ignore-this: 5357f26d2803db2d39159125dddb963a
621 That version of darcsver emits a scary error message when the darcs executable or the _darcs subdirectory is not found.
622 This error is hidden (unless the --loud option is passed) in darcsver >= 1.3.1.
623 Fixes #788.
624]
625[de-Service-ify Helper, pass in storage_broker and secret_holder directly.
626Brian Warner <warner@lothar.com>**20090815201737
627 Ignore-this: 86b8ac0f90f77a1036cd604dd1304d8b
628 This makes it more obvious that the Helper currently generates leases with
629 the Helper's own secrets, rather than getting values from the client, which
630 is arguably a bug that will likely be resolved with the Accounting project.
631]
632[immutable.Downloader: pass StorageBroker to constructor, stop being a Service
633Brian Warner <warner@lothar.com>**20090815192543
634 Ignore-this: af5ab12dbf75377640a670c689838479
635 child of the client, access with client.downloader instead of
636 client.getServiceNamed("downloader"). The single "Downloader" instance is
637 scheduled for demolition anyways, to be replaced by individual
638 filenode.download calls.
639]
640[tests: double the timeout on test_runner.RunNode.test_introducer since feisty hit a timeout
641zooko@zooko.com**20090815160512
642 Ignore-this: ca7358bce4bdabe8eea75dedc39c0e67
643 I'm not sure if this is an actual timing issue (feisty is running on an overloaded VM if I recall correctly), or it there is a deeper bug.
644]
645[stop making History be a Service, it wasn't necessary
646Brian Warner <warner@lothar.com>**20090815114415
647 Ignore-this: b60449231557f1934a751c7effa93cfe
648]
649[Overhaul IFilesystemNode handling, to simplify tests and use POLA internally.
650Brian Warner <warner@lothar.com>**20090815112846
651 Ignore-this: 1db1b9c149a60a310228aba04c5c8e5f
652 
653 * stop using IURI as an adapter
654 * pass cap strings around instead of URI instances
655 * move filenode/dirnode creation duties from Client to new NodeMaker class
656 * move other Client duties to KeyGenerator, SecretHolder, History classes
657 * stop passing Client reference to dirnode/filenode constructors
658   - pass less-powerful references instead, like StorageBroker or Uploader
659 * always create DirectoryNodes by wrapping a filenode (mutable for now)
660 * remove some specialized mock classes from unit tests
661 
662 Detailed list of changes (done one at a time, then merged together)
663 
664 always pass a string to create_node_from_uri(), not an IURI instance
665 always pass a string to IFilesystemNode constructors, not an IURI instance
666 stop using IURI() as an adapter, switch on cap prefix in create_node_from_uri()
667 client.py: move SecretHolder code out to a separate class
668 test_web.py: hush pyflakes
669 client.py: move NodeMaker functionality out into a separate object
670 LiteralFileNode: stop storing a Client reference
671 immutable Checker: remove Client reference, it only needs a SecretHolder
672 immutable Upload: remove Client reference, leave SecretHolder and StorageBroker
673 immutable Repairer: replace Client reference with StorageBroker and SecretHolder
674 immutable FileNode: remove Client reference
675 mutable.Publish: stop passing Client
676 mutable.ServermapUpdater: get StorageBroker in constructor, not by peeking into Client reference
677 MutableChecker: reference StorageBroker and History directly, not through Client
678 mutable.FileNode: removed unused indirection to checker classes
679 mutable.FileNode: remove Client reference
680 client.py: move RSA key generation into a separate class, so it can be passed to the nodemaker
681 move create_mutable_file() into NodeMaker
682 test_dirnode.py: stop using FakeClient mockups, use NoNetworkGrid instead. This simplifies the code, but takes longer to run (17s instead of 6s). This should come down later when other cleanups make it possible to use simpler (non-RSA) fake mutable files for dirnode tests.
683 test_mutable.py: clean up basedir names
684 client.py: move create_empty_dirnode() into NodeMaker
685 dirnode.py: get rid of DirectoryNode.create
686 remove DirectoryNode.init_from_uri, refactor NodeMaker for customization, simplify test_web's mock Client to match
687 stop passing Client to DirectoryNode, make DirectoryNode.create_with_mutablefile the normal DirectoryNode constructor, start removing client from NodeMaker
688 remove Client from NodeMaker
689 move helper status into History, pass History to web.Status instead of Client
690 test_mutable.py: fix minor typo
691]
692[docs: edits for docs/running.html from Sam Mason
693zooko@zooko.com**20090809201416
694 Ignore-this: 2207e80449943ebd4ed50cea57c43143
695]
696[docs: install.html: instruct Debian users to use this document and not to go find the DownloadDebianPackages page, ignore the warning at the top of it, and try it
697zooko@zooko.com**20090804123840
698 Ignore-this: 49da654f19d377ffc5a1eff0c820e026
699 http://allmydata.org/pipermail/tahoe-dev/2009-August/002507.html
700]
701[docs: relnotes.txt: reflow to 63 chars wide because google groups and some web forms seem to wrap to that
702zooko@zooko.com**20090802135016
703 Ignore-this: 53b1493a0491bc30fb2935fad283caeb
704]
705[docs: about.html: fix English usage noticed by Amber
706zooko@zooko.com**20090802050533
707 Ignore-this: 89965c4650f9bd100a615c401181a956
708]
709[docs: fix mis-spelled word in about.html
710zooko@zooko.com**20090802050320
711 Ignore-this: fdfd0397bc7cef9edfde425dddeb67e5
712]
713[TAG allmydata-tahoe-1.5.0
714zooko@zooko.com**20090802031303
715 Ignore-this: 94e5558e7225c39a86aae666ea00f166
716]
717Patch bundle hash:
718299212e00f98eb7b7ce509bcda9b95545cca18d9