from twisted.trial import unittest
from twisted.internet import defer
-import time, os.path, stat
+import time, os.path, stat, re
import itertools
from allmydata import interfaces
from allmydata.util import fileutil, hashutil, base32
ReadBucketProxy
from allmydata.interfaces import BadWriteEnablerError
from allmydata.test.common import LoggingServiceParent
+from allmydata.web.storage import StorageStatus, abbreviate_if_known, \
+ remove_prefix
class Marker:
pass
self.failUnless(abs(output["get"]["95_0_percentile"] - 5) < 1)
self.failUnless(abs(output["get"]["99_0_percentile"] - 5) < 1)
self.failUnless(abs(output["get"]["99_9_percentile"] - 5) < 1)
+
+
+class WebStatus(unittest.TestCase):
+
+ def test_no_server(self):
+ w = StorageStatus(None)
+ html = w.renderSynchronously()
+ self.failUnless("<h1>No Storage Server Running</h1>" in html, html)
+
+
+ def remove_tags(self, s):
+ s = re.sub(r'<[^>]*>', ' ', s)
+ s = re.sub(r'\s+', ' ', s)
+ return s
+
+ def test_status(self):
+ basedir = "storage/WebStatus/status"
+ fileutil.make_dirs(basedir)
+ ss = StorageServer(basedir, "\x00" * 20)
+ w = StorageStatus(ss)
+ html = w.renderSynchronously()
+ self.failUnless("<h1>Storage Server Status</h1>" in html, html)
+ s = self.remove_tags(html)
+ self.failUnless("Accepting new shares: Yes" in s, s)
+ self.failUnless("Reserved space: - 0B" in s, s)
+
+ def test_readonly(self):
+ basedir = "storage/WebStatus/readonly"
+ fileutil.make_dirs(basedir)
+ ss = StorageServer(basedir, "\x00" * 20, readonly_storage=True)
+ w = StorageStatus(ss)
+ html = w.renderSynchronously()
+ self.failUnless("<h1>Storage Server Status</h1>" in html, html)
+ s = self.remove_tags(html)
+ self.failUnless("Accepting new shares: No" in s, s)
+
+ def test_reserved(self):
+ basedir = "storage/WebStatus/reserved"
+ fileutil.make_dirs(basedir)
+ ss = StorageServer(basedir, "\x00" * 20, reserved_space=10e6)
+ w = StorageStatus(ss)
+ html = w.renderSynchronously()
+ self.failUnless("<h1>Storage Server Status</h1>" in html, html)
+ s = self.remove_tags(html)
+ self.failUnless("Reserved space: - 10.00MB" in s, s)
+
+ def test_util(self):
+ self.failUnlessEqual(abbreviate_if_known(None), "?")
+ self.failUnlessEqual(abbreviate_if_known(10e6), "10.00MB")
+ self.failUnlessEqual(remove_prefix("foo.bar", "foo."), "bar")
+ self.failUnlessEqual(remove_prefix("foo.bar", "baz."), None)
+
rend.Page.__init__(self, client)
self.client = client
self.child_operations = operations.OphandleTable()
+ try:
+ s = client.getServiceNamed("storage")
+ except KeyError:
+ s = None
+ self.child_storage = storage.StorageStatus(s)
self.child_uri = URIHandler(client)
self.child_cap = URIHandler(client)
child_reliability = NoReliability()
child_report_incident = IncidentReporter()
+ #child_server # let's reserve this for storage-server-over-HTTP
def data_version(self, ctx, data):
return get_package_versions_string()
ul = T.ul()
try:
ss = self.client.getServiceNamed("storage")
- allocated_s = abbreviate_size(ss.allocated_size())
- allocated = "about %s allocated" % allocated_s
- reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
- ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
+ stats = ss.get_stats()
+ if stats["storage_server.accepting_immutable_shares"]:
+ msg = "accepting new shares"
+ else:
+ msg = "not accepting new shares (read-only)"
+ available = stats.get("storage_server.disk_avail")
+ if available is not None:
+ msg += ", %s available" % abbreviate_size(available)
+ ul[T.li[T.a(href="storage")["Storage Server"], ": ", msg]]
except KeyError:
ul[T.li["Not running storage server"]]
--- /dev/null
+
+from nevow import rend, tags as T
+from allmydata.web.common import getxmlfile, abbreviate_size
+
+def abbreviate_if_known(size):
+ if size is None:
+ return "?"
+ return abbreviate_size(size)
+
+def remove_prefix(s, prefix):
+ if not s.startswith(prefix):
+ return None
+ return s[len(prefix):]
+
+class StorageStatus(rend.Page):
+ docFactory = getxmlfile("storage_status.xhtml")
+ # the default 'data' argument is the StorageServer instance
+
+ def __init__(self, storage):
+ rend.Page.__init__(self, storage)
+ self.storage = storage
+
+ def render_storage_running(self, ctx, storage):
+ if storage:
+ return ctx.tag
+ else:
+ return T.h1["No Storage Server Running"]
+
+ def render_bool(self, ctx, data):
+ return {True: "Yes", False: "No"}[bool(data)]
+
+ def render_space(self, ctx, data):
+ return abbreviate_if_known(data)
+
+ def data_stats(self, ctx, data):
+ # FYI: 'data' appears to be self, rather than the StorageServer
+ # object in self.original that gets passed to render_* methods. I
+ # still don't understand Nevow.
+
+ # all xhtml tags that are children of a tag with n:render="stats"
+ # will be processed with this dictionary, so something like:
+ # <ul n:data="stats">
+ # <li>disk_total: <span n:data="disk_total" /></li>
+ # </ul>
+ # will use get_stats()["storage_server.disk_total"]
+ return dict([ (remove_prefix(k, "storage_server."), v)
+ for k,v in self.storage.get_stats().items() ])
--- /dev/null
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+ <head>
+ <title>AllMyData - Tahoe - Storage Server Status</title>
+ <link href="/webform_css" rel="stylesheet" type="text/css"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ </head>
+<body>
+
+<div n:render="storage_running">
+
+ <h1>Storage Server Status</h1>
+
+ <ul n:data="stats">
+ <li>Accepting new shares:
+ <span n:render="bool" n:data="accepting_immutable_shares" /></li>
+ </ul>
+
+ <table n:data="stats">
+ <tr><td>Total disk space:</td>
+ <td><span n:render="space" n:data="disk_total" /></td></tr>
+ <tr><td>Disk space used:</td>
+ <td>- <span n:render="space" n:data="disk_used" /></td></tr>
+ <tr><td>Reserved space:</td>
+ <td>- <span n:render="space" n:data="reserved_space" /></td></tr>
+ <tr><td />
+ <td>======</td></tr>
+ <tr><td>Space Available:</td>
+ <td>< <span n:render="space" n:data="disk_avail" /></td></tr>
+ </table>
+
+</div>
+
+</body>
+</html>