mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-20 13:33:09 +00:00
web: add Storage status page, improve tests
This commit is contained in:
parent
ff6907a557
commit
00677ff9a5
@ -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)
|
||||
|
||||
|
@ -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"]]
|
||||
|
||||
|
47
src/allmydata/web/storage.py
Normal file
47
src/allmydata/web/storage.py
Normal file
@ -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() ])
|
34
src/allmydata/web/storage_status.xhtml
Normal file
34
src/allmydata/web/storage_status.xhtml
Normal file
@ -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>
|
Loading…
Reference in New Issue
Block a user