1 | diff -rN old-tahoe/_auto_deps.py new-tahoe/_auto_deps.py |
---|
2 | 40,41c40,42 |
---|
3 | < ## can be easy_install'ed and also which actually makes "import win32api" succeed. Users have |
---|
4 | < ## to manually install pywin32 on Windows before installing Tahoe. |
---|
5 | --- |
---|
6 | > ## can be easy_install'ed and also which actually makes "import win32api" succeed. |
---|
7 | > ## See http://sourceforge.net/tracker/index.php?func=detail&aid=1799934&group_id=78018&atid=551954 |
---|
8 | > ## Users have to manually install pywin32 on Windows before installing Tahoe. |
---|
9 | 47,52c48,51 |
---|
10 | < ## # management. Then the specification and the evolution of Twisted's reliance on pywin32 can |
---|
11 | < ## # be confined to the Twisted setup data, and Tahoe can remain blissfully ignorant about such |
---|
12 | < ## # things as if a future version of Twisted requires a different version of pywin32, or if a |
---|
13 | < ## # future version of Twisted implements process management without using pywin32 at all, |
---|
14 | < ## # etc.. That is twisted ticket #3238 -- http://twistedmatrix.com/trac/ticket/3238 . But |
---|
15 | < ## # until Twisted does that, Tahoe needs to be non-ignorant of the following requirement: |
---|
16 | --- |
---|
17 | > ## # management. That is twisted ticket #3238 -- http://twistedmatrix.com/trac/ticket/3238 . |
---|
18 | > ## # On the other hand, Tahoe also depends on pywin32 for getting free disk space statistics |
---|
19 | > ## # (although that is not a hard requirement: if win32api can't be imported then we don't |
---|
20 | > ## # rely on having the disk stats). |
---|
21 | diff -rN old-tahoe/docs/configuration.txt new-tahoe/docs/configuration.txt |
---|
22 | 305,306c305,308 |
---|
23 | < server will not accept any share which causes the amount of free space (as |
---|
24 | < measured by 'df', or more specifically statvfs(2)) to drop below this value. |
---|
25 | --- |
---|
26 | > server will not accept any share which causes the amount of free disk space |
---|
27 | > to drop below this value. (The free space is measured by a call to statvfs(2) |
---|
28 | > on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the |
---|
29 | > user account under which the storage server runs.) |
---|
30 | diff -rN old-tahoe/src/allmydata/storage/server.py new-tahoe/src/allmydata/storage/server.py |
---|
31 | 38a39,47 |
---|
32 | > windows = False |
---|
33 | > |
---|
34 | > try: |
---|
35 | > import win32api, win32con |
---|
36 | > windows = True |
---|
37 | > # <http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx> |
---|
38 | > win32api.SetErrorMode(win32con.SEM_FAILCRITICALERRORS | win32con.SEM_NOOPENFILEERRORBOX) |
---|
39 | > except ImportError: |
---|
40 | > pass |
---|
41 | 73c82 |
---|
42 | < log.msg("warning: [storage]reserved_space= is set, but this platform does not support statvfs(2), so this reservation cannot be honored", |
---|
43 | --- |
---|
44 | > 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", |
---|
45 | 150,151c159,183 |
---|
46 | < def do_statvfs(self): |
---|
47 | < return os.statvfs(self.storedir) |
---|
48 | --- |
---|
49 | > def get_disk_stats(self): |
---|
50 | > """Return disk statistics for the storage disk, in the form of a dict with the following fields. |
---|
51 | > total: total bytes on disk |
---|
52 | > free_for_root: bytes actually free on disk |
---|
53 | > free_for_nonroot: bytes free for "a non-privileged user" [Unix] or the current user [Windows]; |
---|
54 | > might take into account quotas depending on platform |
---|
55 | > used: bytes used on disk |
---|
56 | > avail: bytes available excluding reserved space |
---|
57 | > An AttributeError can occur if the OS has no API to get disk information. |
---|
58 | > An EnvironmentError can occur if the OS call fails.""" |
---|
59 | > |
---|
60 | > if self.windows: |
---|
61 | > # For Windows systems, where os.statvfs is not available, use GetDiskFreeSpaceEx. |
---|
62 | > # <http://docs.activestate.com/activepython/2.5/pywin32/win32api__GetDiskFreeSpaceEx_meth.html> |
---|
63 | > # |
---|
64 | > # Although the docs say that the argument should be the root directory of a disk, GetDiskFreeSpaceEx |
---|
65 | > # actually accepts any path on that disk (like its Win32 equivalent). |
---|
66 | > |
---|
67 | > (free_for_nonroot, total, free_for_root) = self.win32api.GetDiskFreeSpaceEx(self.storedir) |
---|
68 | > else: |
---|
69 | > # For Unix-like systems. |
---|
70 | > # <http://docs.python.org/library/os.html#os.statvfs> |
---|
71 | > # <http://opengroup.org/onlinepubs/7990989799/xsh/fstatvfs.html> |
---|
72 | > # <http://opengroup.org/onlinepubs/7990989799/xsh/sysstatvfs.h.html> |
---|
73 | > s = os.statvfs(self.storedir) |
---|
74 | 153,165d184 |
---|
75 | < def get_stats(self): |
---|
76 | < # remember: RIStatsProvider requires that our return dict |
---|
77 | < # contains numeric values. |
---|
78 | < stats = { 'storage_server.allocated': self.allocated_size(), } |
---|
79 | < stats["storage_server.reserved_space"] = self.reserved_space |
---|
80 | < for category,ld in self.get_latencies().items(): |
---|
81 | < for name,v in ld.items(): |
---|
82 | < stats['storage_server.latencies.%s.%s' % (category, name)] = v |
---|
83 | < writeable = True |
---|
84 | < if self.readonly_storage: |
---|
85 | < writeable = False |
---|
86 | < try: |
---|
87 | < s = self.do_statvfs() |
---|
88 | 176,190c195,217 |
---|
89 | < disk_total = s.f_frsize * s.f_blocks |
---|
90 | < disk_used = s.f_frsize * (s.f_blocks - s.f_bfree) |
---|
91 | < # spacetime predictors should look at the slope of disk_used. |
---|
92 | < disk_free_for_root = s.f_frsize * s.f_bfree |
---|
93 | < disk_free_for_nonroot = s.f_frsize * s.f_bavail |
---|
94 | < |
---|
95 | < # include our local policy here: if we stop accepting shares when |
---|
96 | < # the available space drops below 1GB, then include that fact in |
---|
97 | < # disk_avail. |
---|
98 | < disk_avail = disk_free_for_nonroot - self.reserved_space |
---|
99 | < disk_avail = max(disk_avail, 0) |
---|
100 | < if self.readonly_storage: |
---|
101 | < disk_avail = 0 |
---|
102 | < if disk_avail == 0: |
---|
103 | < writeable = False |
---|
104 | --- |
---|
105 | > total = s.f_frsize * s.f_blocks |
---|
106 | > free_for_root = s.f_frsize * s.f_bfree |
---|
107 | > free_for_nonroot = s.f_frsize * s.f_bavail |
---|
108 | > |
---|
109 | > # valid for all platforms: |
---|
110 | > used = total - free_for_root |
---|
111 | > avail = max(free_for_nonroot - self.reserved_space, 0) |
---|
112 | > |
---|
113 | > return { 'total': total, 'free_for_root': free_for_root, 'free_for_nonroot': free_for_nonroot, |
---|
114 | > 'used': used, 'avail': avail, } |
---|
115 | > |
---|
116 | > def get_stats(self): |
---|
117 | > # remember: RIStatsProvider requires that our return dict |
---|
118 | > # contains numeric values. |
---|
119 | > stats = { 'storage_server.allocated': self.allocated_size(), } |
---|
120 | > stats['storage_server.reserved_space'] = self.reserved_space |
---|
121 | > for category,ld in self.get_latencies().items(): |
---|
122 | > for name,v in ld.items(): |
---|
123 | > stats['storage_server.latencies.%s.%s' % (category, name)] = v |
---|
124 | > |
---|
125 | > try: |
---|
126 | > disk = self.get_disk_stats() |
---|
127 | > writeable = disk['avail'] > 0 |
---|
128 | 193,197c220,224 |
---|
129 | < stats["storage_server.disk_total"] = disk_total |
---|
130 | < stats["storage_server.disk_used"] = disk_used |
---|
131 | < stats["storage_server.disk_free_for_root"] = disk_free_for_root |
---|
132 | < stats["storage_server.disk_free_for_nonroot"] = disk_free_for_nonroot |
---|
133 | < stats["storage_server.disk_avail"] = disk_avail |
---|
134 | --- |
---|
135 | > stats['storage_server.disk_total'] = disk['total'] |
---|
136 | > stats['storage_server.disk_used'] = disk['used'] |
---|
137 | > stats['storage_server.disk_free_for_root'] = disk['free_for_root'] |
---|
138 | > stats['storage_server.disk_free_for_nonroot'] = disk['free_for_nonroot'] |
---|
139 | > stats['storage_server.disk_avail'] = disk['avail'] |
---|
140 | 199,201c226,235 |
---|
141 | < # os.statvfs is available only on unix |
---|
142 | < pass |
---|
143 | < stats["storage_server.accepting_immutable_shares"] = int(writeable) |
---|
144 | --- |
---|
145 | > writeable = True |
---|
146 | > except EnvironmentError: |
---|
147 | > log.msg("OS call to get disk statistics failed", level=log.UNUSUAL) |
---|
148 | > writeable = False |
---|
149 | > |
---|
150 | > if self.readonly_storage: |
---|
151 | > stats['storage_server.disk_avail'] = 0 |
---|
152 | > writeable = False |
---|
153 | > |
---|
154 | > stats['storage_server.accepting_immutable_shares'] = int(writeable) |
---|
155 | 205c239 |
---|
156 | < stats["storage_server.total_bucket_count"] = bucket_count |
---|
157 | --- |
---|
158 | > stats['storage_server.total_bucket_count'] = bucket_count |
---|
159 | 208,214d241 |
---|
160 | < |
---|
161 | < def stat_disk(self, d): |
---|
162 | < s = os.statvfs(d) |
---|
163 | < # s.f_bavail: available to non-root users |
---|
164 | < disk_avail = s.f_frsize * s.f_bavail |
---|
165 | < return disk_avail |
---|
166 | < |
---|
167 | 216c243,246 |
---|
168 | < # returns None if it cannot be measured (windows) |
---|
169 | --- |
---|
170 | > """Returns available space for share storage in bytes, or None if no API to get this |
---|
171 | > information is available.""" |
---|
172 | > if self.readonly_storage: |
---|
173 | > return 0 |
---|
174 | 218,219c248 |
---|
175 | < disk_avail = self.stat_disk(self.storedir) |
---|
176 | < disk_avail -= self.reserved_space |
---|
177 | --- |
---|
178 | > return self.get_disk_stats()['avail'] |
---|
179 | 221,224c250,253 |
---|
180 | < disk_avail = None |
---|
181 | < if self.readonly_storage: |
---|
182 | < disk_avail = 0 |
---|
183 | < return disk_avail |
---|
184 | --- |
---|
185 | > return None |
---|
186 | > except EnvironmentError: |
---|
187 | > log.msg("OS call to get disk statistics failed", level=log.UNUSUAL) |
---|
188 | > return 0 |
---|
189 | 235,236c264 |
---|
190 | < # we're on a platform that doesn't have 'df', so make a vague |
---|
191 | < # guess. |
---|
192 | --- |
---|
193 | > # We're on a platform that has no API to get disk stats. |
---|
194 | 237a266 |
---|
195 | > |
---|
196 | 291c320 |
---|
197 | < # self.readonly_storage causes remaining_space=0 |
---|
198 | --- |
---|
199 | > # self.readonly_storage causes remaining_space <= 0 |
---|
200 | diff -rN old-tahoe/src/allmydata/test/test_storage.py new-tahoe/src/allmydata/test/test_storage.py |
---|
201 | 231,232c231,233 |
---|
202 | < def stat_disk(self, d): |
---|
203 | < return self.DISKAVAIL |
---|
204 | --- |
---|
205 | > DISKAVAIL = 0 |
---|
206 | > def get_disk_stats(self): |
---|
207 | > return { 'free_for_nonroot': self.DISKAVAIL, 'avail': max(self.DISKAVAIL - self.reserved_space, 0), } |
---|
208 | 415c416 |
---|
209 | < # the FakeDiskStorageServer doesn't do real statvfs() calls |
---|
210 | --- |
---|
211 | > # the FakeDiskStorageServer doesn't do real calls to get_disk_stats |
---|
212 | 470a472,488 |
---|
213 | > def test_disk_stats(self): |
---|
214 | > # This will spuriously fail if there is zero disk space left (but so will other tests). |
---|
215 | > ss = self.create("test_disk_stats", reserved_space=0) |
---|
216 | > |
---|
217 | > disk = ss.get_disk_stats() |
---|
218 | > self.failUnless(disk['total'] > 0, disk['total']) |
---|
219 | > self.failUnless(disk['used'] > 0, disk['used']) |
---|
220 | > self.failUnless(disk['free_for_root'] > 0, disk['free_for_root']) |
---|
221 | > self.failUnless(disk['free_for_nonroot'] > 0, disk['free_for_nonroot']) |
---|
222 | > self.failUnless(disk['avail'] > 0, disk['avail']) |
---|
223 | > |
---|
224 | > def test_disk_stats_avail_nonnegative(self): |
---|
225 | > ss = self.create("test_disk_stats_avail_nonnegative", reserved_space=2**64) |
---|
226 | > |
---|
227 | > disk = ss.get_disk_stats() |
---|
228 | > self.failUnlessEqual(disk['avail'], 0) |
---|
229 | > |
---|
230 | 627,628c645 |
---|
231 | < self.failUnlessEqual(stats["storage_server.accepting_immutable_shares"], |
---|
232 | < False) |
---|
233 | --- |
---|
234 | > self.failUnlessEqual(stats["storage_server.accepting_immutable_shares"], 0) |
---|
235 | 630,632c647,648 |
---|
236 | < # windows does not have os.statvfs, so it doesn't give us disk |
---|
237 | < # stats. But if there are stats, readonly_storage means |
---|
238 | < # disk_avail=0 |
---|
239 | --- |
---|
240 | > # Some platforms may not have an API to get disk stats. |
---|
241 | > # But if there are stats, readonly_storage means disk_avail=0 |
---|
242 | 2408,2409c2424,2425 |
---|
243 | < class NoStatvfsServer(StorageServer): |
---|
244 | < def do_statvfs(self): |
---|
245 | --- |
---|
246 | > class NoDiskStatsServer(StorageServer): |
---|
247 | > def get_disk_stats(self): |
---|
248 | 2411a2428,2431 |
---|
249 | > class BadDiskStatsServer(StorageServer): |
---|
250 | > def get_disk_stats(self): |
---|
251 | > raise OSError |
---|
252 | > |
---|
253 | 2453,2456c2473,2476 |
---|
254 | < def test_status_no_statvfs(self): |
---|
255 | < # windows has no os.statvfs . Make sure the code handles that even on |
---|
256 | < # unix. |
---|
257 | < basedir = "storage/WebStatus/status_no_statvfs" |
---|
258 | --- |
---|
259 | > def test_status_no_disk_stats(self): |
---|
260 | > # Some platforms may have no disk stats API. Make sure the code can handle that |
---|
261 | > # (test runs on all platforms). |
---|
262 | > basedir = "storage/WebStatus/status_no_disk_stats" |
---|
263 | 2458c2478 |
---|
264 | < ss = NoStatvfsServer(basedir, "\x00" * 20) |
---|
265 | --- |
---|
266 | > ss = NoDiskStatsServer(basedir, "\x00" * 20) |
---|
267 | 2465a2486,2503 |
---|
268 | > self.failUnless("Space Available to Tahoe: ?" in s, s) |
---|
269 | > self.failUnless(ss.get_available_space() is None) |
---|
270 | > |
---|
271 | > def test_status_bad_disk_stats(self): |
---|
272 | > # If the API to get disk stats exists but a call to it fails, then the status should |
---|
273 | > # show that no shares will be accepted, and get_available_space() should be 0. |
---|
274 | > basedir = "storage/WebStatus/status_bad_disk_stats" |
---|
275 | > fileutil.make_dirs(basedir) |
---|
276 | > ss = BadDiskStatsServer(basedir, "\x00" * 20) |
---|
277 | > ss.setServiceParent(self.s) |
---|
278 | > w = StorageStatus(ss) |
---|
279 | > html = w.renderSynchronously() |
---|
280 | > self.failUnless("<h1>Storage Server Status</h1>" in html, html) |
---|
281 | > s = remove_tags(html) |
---|
282 | > self.failUnless("Accepting new shares: No" in s, s) |
---|
283 | > self.failUnless("Total disk space: ?" in s, s) |
---|
284 | > self.failUnless("Space Available to Tahoe: ?" in s, s) |
---|
285 | > self.failUnless(ss.get_available_space() == 0) |
---|