From 00677ff9a5877e77160fc22105898dee85d2078d Mon Sep 17 00:00:00 2001 From: Brian Warner <warner@lothar.com> Date: Fri, 20 Feb 2009 14:29:26 -0700 Subject: [PATCH] web: add Storage status page, improve tests --- src/allmydata/test/test_storage.py | 56 +++++++++++++++++++++++++- src/allmydata/web/root.py | 19 +++++++-- src/allmydata/web/storage.py | 47 +++++++++++++++++++++ src/allmydata/web/storage_status.xhtml | 34 ++++++++++++++++ 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/allmydata/web/storage.py create mode 100644 src/allmydata/web/storage_status.xhtml diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index f2c9ac5b..d5723247 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -1,7 +1,7 @@ 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 @@ -14,6 +14,8 @@ from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \ 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 @@ -1287,3 +1289,55 @@ class Stats(unittest.TestCase): 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) + diff --git a/src/allmydata/web/root.py b/src/allmydata/web/root.py index e82d5758..79441c25 100644 --- a/src/allmydata/web/root.py +++ b/src/allmydata/web/root.py @@ -142,6 +142,11 @@ class Root(rend.Page): 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) @@ -170,6 +175,7 @@ class Root(rend.Page): 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() @@ -184,10 +190,15 @@ class Root(rend.Page): 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"]] diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py new file mode 100644 index 00000000..013cedb9 --- /dev/null +++ b/src/allmydata/web/storage.py @@ -0,0 +1,47 @@ + +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() ]) diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml new file mode 100644 index 00000000..49139446 --- /dev/null +++ b/src/allmydata/web/storage_status.xhtml @@ -0,0 +1,34 @@ +<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> -- 2.45.2