mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-25 07:31:07 +00:00
Handle all the status cases
This commit is contained in:
parent
91c655298a
commit
20084506b3
@ -83,6 +83,163 @@ def pretty_progress(percent, size=10, ascii=False):
|
|||||||
curr = int(curr)
|
curr = int(curr)
|
||||||
return '%s%s%s' % ((block_chr * curr), part, (' ' * (size - curr - 1)))
|
return '%s%s%s' % ((block_chr * curr), part, (' ' * (size - curr - 1)))
|
||||||
|
|
||||||
|
OP_MAP = {
|
||||||
|
'upload': ' put ',
|
||||||
|
'download': ' get ',
|
||||||
|
'retrieve': 'retr ',
|
||||||
|
'publish': ' pub ',
|
||||||
|
'mapupdate': 'mapup',
|
||||||
|
'unknown': ' ??? ',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _render_active_upload(op):
|
||||||
|
total = (
|
||||||
|
op['progress-hash'] +
|
||||||
|
op['progress-ciphertext'] +
|
||||||
|
op['progress-encode-push']
|
||||||
|
) / 3.0 * 100.0
|
||||||
|
return {
|
||||||
|
u"op_type": u" put ",
|
||||||
|
u"total": "{:3.0f}".format(total),
|
||||||
|
u"progress_bar": u"{}".format(pretty_progress(total, size=15)),
|
||||||
|
u"storage-index-string": op["storage-index-string"],
|
||||||
|
u"status": op["status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _render_active_download(op):
|
||||||
|
return {
|
||||||
|
u"op_type": u" get ",
|
||||||
|
u"total": op["progress"],
|
||||||
|
u"progress_bar": u"{}".format(pretty_progress(op['progress'] * 100.0, size=15)),
|
||||||
|
u"storage-index-string": op["storage-index-string"],
|
||||||
|
u"status": op["status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _render_active_generic(op):
|
||||||
|
return {
|
||||||
|
u"op_type": OP_MAP[op["type"]],
|
||||||
|
u"progress_bar": u"",
|
||||||
|
u"total": u"???",
|
||||||
|
u"storage-index-string": op["storage-index-string"],
|
||||||
|
u"status": op["status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
active_renderers = {
|
||||||
|
"upload": _render_active_upload,
|
||||||
|
"download": _render_active_download,
|
||||||
|
"publish": _render_active_generic,
|
||||||
|
"retrieve": _render_active_generic,
|
||||||
|
"mapupdate": _render_active_generic,
|
||||||
|
"unknown": _render_active_generic,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def render_active(stdout, status_data):
|
||||||
|
active = status_data.get('active', None)
|
||||||
|
if not active:
|
||||||
|
print(u"No active operations.", file=stdout)
|
||||||
|
return
|
||||||
|
|
||||||
|
header = u"\u2553 {:<5} \u2565 {:<26} \u2565 {:<22} \u2565 {}".format(
|
||||||
|
"type",
|
||||||
|
"storage index",
|
||||||
|
"progress",
|
||||||
|
"status message",
|
||||||
|
)
|
||||||
|
header_bar = u"\u255f\u2500{}\u2500\u256b\u2500{}\u2500\u256b\u2500{}\u2500\u256b\u2500{}".format(
|
||||||
|
u'\u2500' * 5,
|
||||||
|
u'\u2500' * 26,
|
||||||
|
u'\u2500' * 22,
|
||||||
|
u'\u2500' * 20,
|
||||||
|
)
|
||||||
|
line_template = (
|
||||||
|
u"\u2551 {op_type} "
|
||||||
|
u"\u2551 {storage-index-string} "
|
||||||
|
u"\u2551 {progress_bar:15} "
|
||||||
|
u"({total}%) "
|
||||||
|
u"\u2551 {status}"
|
||||||
|
)
|
||||||
|
footer_bar = u"\u2559\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}".format(
|
||||||
|
u'\u2500' * 5,
|
||||||
|
u'\u2500' * 26,
|
||||||
|
u'\u2500' * 22,
|
||||||
|
u'\u2500' * 20,
|
||||||
|
)
|
||||||
|
print(u"Active operations:", file=stdout)
|
||||||
|
print(header, file=stdout)
|
||||||
|
print(header_bar, file=stdout)
|
||||||
|
for op in active:
|
||||||
|
print(line_template.format(
|
||||||
|
**active_renderers[op["type"]](op)
|
||||||
|
))
|
||||||
|
print(footer_bar, file=stdout)
|
||||||
|
|
||||||
|
def _render_recent_generic(op):
|
||||||
|
return {
|
||||||
|
u"op_type": OP_MAP[op["type"]],
|
||||||
|
u"storage-index-string": op["storage-index-string"],
|
||||||
|
u"nice_size": abbreviate_space(op["total-size"]),
|
||||||
|
u"status": op["status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _render_recent_mapupdate(op):
|
||||||
|
return {
|
||||||
|
u"op_type": u"mapup",
|
||||||
|
u"storage-index-string": op["storage-index-string"],
|
||||||
|
u"nice_size": op["mode"],
|
||||||
|
u"status": op["status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
recent_renderers = {
|
||||||
|
"upload": _render_recent_generic,
|
||||||
|
"download": _render_recent_generic,
|
||||||
|
"publish": _render_recent_generic,
|
||||||
|
"retrieve": _render_recent_generic,
|
||||||
|
"mapupdate": _render_recent_mapupdate,
|
||||||
|
"unknown": _render_recent_generic,
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_recent(verbose, stdout, status_data):
|
||||||
|
recent = status_data.get('recent', None)
|
||||||
|
if not recent:
|
||||||
|
print(u"No recent operations.", file=stdout)
|
||||||
|
|
||||||
|
header = u"\u2553 {:<5} \u2565 {:<26} \u2565 {:<10} \u2565 {}".format(
|
||||||
|
"type",
|
||||||
|
"storage index",
|
||||||
|
"size",
|
||||||
|
"status message",
|
||||||
|
)
|
||||||
|
line_template = (
|
||||||
|
u"\u2551 {op_type} "
|
||||||
|
u"\u2551 {storage-index-string} "
|
||||||
|
u"\u2551 {nice_size:<10} "
|
||||||
|
u"\u2551 {status}"
|
||||||
|
)
|
||||||
|
footer = u"\u2559\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}".format(
|
||||||
|
u'\u2500' * 5,
|
||||||
|
u'\u2500' * 26,
|
||||||
|
u'\u2500' * 10,
|
||||||
|
u'\u2500' * 20,
|
||||||
|
)
|
||||||
|
non_verbose_ops = ('upload', 'download')
|
||||||
|
recent = [op for op in status_data['recent'] if op['type'] in non_verbose_ops]
|
||||||
|
print(u"\nRecent operations:", file=stdout)
|
||||||
|
if len(recent) or verbose:
|
||||||
|
print(header, file=stdout)
|
||||||
|
|
||||||
|
ops_to_show = status_data['recent'] if verbose else recent
|
||||||
|
for op in ops_to_show:
|
||||||
|
print(line_template.format(
|
||||||
|
**recent_renderers[op["type"]](op)
|
||||||
|
))
|
||||||
|
if len(recent) or verbose:
|
||||||
|
print(footer, file=stdout)
|
||||||
|
|
||||||
|
skipped = len(status_data['recent']) - len(ops_to_show)
|
||||||
|
if not verbose and skipped:
|
||||||
|
print(u" Skipped {} non-upload/download operations; use --verbose to see".format(skipped), file=stdout)
|
||||||
|
|
||||||
|
|
||||||
def do_status(options):
|
def do_status(options):
|
||||||
nodedir = options["node-directory"]
|
nodedir = options["node-directory"]
|
||||||
@ -125,83 +282,8 @@ def do_status(options):
|
|||||||
print(u" downloaded {} in {} files".format(abbreviate_space(downloaded_bytes), downloaded_files), file=options.stdout)
|
print(u" downloaded {} in {} files".format(abbreviate_space(downloaded_bytes), downloaded_files), file=options.stdout)
|
||||||
print(u"", file=options.stdout)
|
print(u"", file=options.stdout)
|
||||||
|
|
||||||
if status_data.get('active', None):
|
render_active(options.stdout, status_data)
|
||||||
print(u"Active operations:", file=options.stdout)
|
render_recent(options['verbose'], options.stdout, status_data)
|
||||||
print(
|
|
||||||
u"\u2553 {:<5} \u2565 {:<26} \u2565 {:<22} \u2565 {}".format(
|
|
||||||
"type",
|
|
||||||
"storage index",
|
|
||||||
"progress",
|
|
||||||
"status message",
|
|
||||||
), file=options.stdout
|
|
||||||
)
|
|
||||||
print(u"\u255f\u2500{}\u2500\u256b\u2500{}\u2500\u256b\u2500{}\u2500\u256b\u2500{}".format(u'\u2500' * 5, u'\u2500' * 26, u'\u2500' * 22, u'\u2500' * 20), file=options.stdout)
|
|
||||||
for op in status_data['active']:
|
|
||||||
if 'progress-hash' in op:
|
|
||||||
op_type = ' put '
|
|
||||||
total = (op['progress-hash'] + op['progress-ciphertext'] + op['progress-encode-push']) / 3.0
|
|
||||||
progress_bar = u"{}".format(pretty_progress(total * 100.0, size=15))
|
|
||||||
else:
|
|
||||||
op_type = ' get '
|
|
||||||
total = op['progress']
|
|
||||||
progress_bar = u"{}".format(pretty_progress(op['progress'] * 100.0, size=15))
|
|
||||||
print(
|
|
||||||
u"\u2551 {op_type} \u2551 {storage-index-string} \u2551 {progress_bar} ({total:3}%) \u2551 {status}".format(
|
|
||||||
op_type=op_type,
|
|
||||||
progress_bar=progress_bar,
|
|
||||||
total=int(total * 100.0),
|
|
||||||
**op
|
|
||||||
), file=options.stdout
|
|
||||||
)
|
|
||||||
|
|
||||||
print(u"\u2559\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}".format(u'\u2500' * 5, u'\u2500' * 26, u'\u2500' * 22, u'\u2500' * 20), file=options.stdout)
|
|
||||||
else:
|
|
||||||
print(u"No active operations.", file=options.stdout)
|
|
||||||
|
|
||||||
if status_data.get('recent', None):
|
|
||||||
non_verbose_ops = ('upload', 'download')
|
|
||||||
recent = [op for op in status_data['recent'] if op['type'] in non_verbose_ops]
|
|
||||||
print(u"\nRecent operations:", file=options.stdout)
|
|
||||||
if len(recent) or options['verbose']:
|
|
||||||
print(
|
|
||||||
u"\u2553 {:<5} \u2565 {:<26} \u2565 {:<10} \u2565 {}".format(
|
|
||||||
"type",
|
|
||||||
"storage index",
|
|
||||||
"size",
|
|
||||||
"status message",
|
|
||||||
), file=options.stdout
|
|
||||||
)
|
|
||||||
|
|
||||||
op_map = {
|
|
||||||
'upload': ' put ',
|
|
||||||
'download': ' get ',
|
|
||||||
'retrieve': 'retr ',
|
|
||||||
'publish': ' pub ',
|
|
||||||
'mapupdate': 'mapup',
|
|
||||||
}
|
|
||||||
|
|
||||||
ops_to_show = status_data['recent'] if options['verbose'] else recent
|
|
||||||
for op in ops_to_show:
|
|
||||||
op_type = op_map[op.get('type', None)]
|
|
||||||
if op['type'] == 'mapupdate':
|
|
||||||
nice_size = op['mode']
|
|
||||||
else:
|
|
||||||
nice_size = abbreviate_space(op['total-size'])
|
|
||||||
print(
|
|
||||||
u"\u2551 {op_type} \u2551 {storage-index-string} \u2551 {nice_size:<10} \u2551 {status}".format(
|
|
||||||
op_type=op_type,
|
|
||||||
nice_size=nice_size,
|
|
||||||
**op
|
|
||||||
), file=options.stdout
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(recent) or options['verbose']:
|
|
||||||
print(u"\u2559\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}\u2500\u2568\u2500{}".format(u'\u2500' * 5, u'\u2500' * 26, u'\u2500' * 10, u'\u2500' * 20), file=options.stdout)
|
|
||||||
skipped = len(status_data['recent']) - len(ops_to_show)
|
|
||||||
if not options['verbose'] and skipped:
|
|
||||||
print(u" Skipped {} non-upload/download operations; use --verbose to see".format(skipped), file=options.stdout)
|
|
||||||
else:
|
|
||||||
print(u"No recent operations.", file=options.stdout)
|
|
||||||
|
|
||||||
# open question: should we return non-zero if there were no
|
# open question: should we return non-zero if there were no
|
||||||
# operations at all to display?
|
# operations at all to display?
|
||||||
|
@ -15,9 +15,17 @@ from allmydata.scripts.tahoe_status import _get_json_for_fragment
|
|||||||
from allmydata.scripts.tahoe_status import _get_json_for_cap
|
from allmydata.scripts.tahoe_status import _get_json_for_cap
|
||||||
from allmydata.scripts.tahoe_status import pretty_progress
|
from allmydata.scripts.tahoe_status import pretty_progress
|
||||||
from allmydata.scripts.tahoe_status import do_status
|
from allmydata.scripts.tahoe_status import do_status
|
||||||
|
from allmydata.web.status import marshal_json
|
||||||
|
|
||||||
|
from allmydata.immutable.upload import UploadStatus
|
||||||
|
from allmydata.immutable.downloader.status import DownloadStatus
|
||||||
|
from allmydata.mutable.publish import PublishStatus
|
||||||
|
from allmydata.mutable.retrieve import RetrieveStatus
|
||||||
|
from allmydata.mutable.servermap import UpdateStatus
|
||||||
|
|
||||||
from ..no_network import GridTestMixin
|
from ..no_network import GridTestMixin
|
||||||
from ..common_web import do_http
|
from ..common_web import do_http
|
||||||
|
from ..status import FakeStatus
|
||||||
from .common import CLITestMixin
|
from .common import CLITestMixin
|
||||||
|
|
||||||
|
|
||||||
@ -135,30 +143,26 @@ class CommandStatus(unittest.TestCase):
|
|||||||
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
@mock.patch('allmydata.scripts.tahoe_status.do_http')
|
||||||
@mock.patch('sys.stdout', StringIO())
|
@mock.patch('sys.stdout', StringIO())
|
||||||
def test_simple(self, http):
|
def test_simple(self, http):
|
||||||
|
recent_items = active_items = [
|
||||||
|
UploadStatus(),
|
||||||
|
DownloadStatus("abcd", 12345),
|
||||||
|
PublishStatus(),
|
||||||
|
RetrieveStatus(),
|
||||||
|
UpdateStatus(),
|
||||||
|
FakeStatus(),
|
||||||
|
]
|
||||||
values = [
|
values = [
|
||||||
StringIO(json.dumps({
|
StringIO(json.dumps({
|
||||||
"active": [
|
"active": list(
|
||||||
{
|
marshal_json(item)
|
||||||
"progress": 0.5,
|
for item
|
||||||
"storage-index-string": "index0",
|
in active_items
|
||||||
"status": "foo",
|
),
|
||||||
},
|
"recent": list(
|
||||||
{
|
marshal_json(item)
|
||||||
"progress-hash": 1.0,
|
for item
|
||||||
"progress-ciphertext": 1.0,
|
in recent_items
|
||||||
"progress-encode-push": 0.5,
|
),
|
||||||
"storage-index-string": "index1",
|
|
||||||
"status": "bar",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"recent": [
|
|
||||||
{
|
|
||||||
"type": "download",
|
|
||||||
"total-size": 12345,
|
|
||||||
"storage-index-string": "index1",
|
|
||||||
"status": "bar",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})),
|
})),
|
||||||
StringIO(json.dumps({
|
StringIO(json.dumps({
|
||||||
"counters": {
|
"counters": {
|
||||||
|
16
src/allmydata/test/status.py
Normal file
16
src/allmydata/test/status.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
class FakeStatus(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.status = []
|
||||||
|
|
||||||
|
def setServiceParent(self, p):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return self.status
|
||||||
|
|
||||||
|
def get_storage_index(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_size(self):
|
||||||
|
return None
|
@ -50,6 +50,8 @@ from ..common_web import (
|
|||||||
)
|
)
|
||||||
from allmydata.client import _Client, SecretHolder
|
from allmydata.client import _Client, SecretHolder
|
||||||
from .common import unknown_rwcap, unknown_rocap, unknown_immcap, FAVICON_MARKUP
|
from .common import unknown_rwcap, unknown_rocap, unknown_immcap, FAVICON_MARKUP
|
||||||
|
from ..status import FakeStatus
|
||||||
|
|
||||||
# create a fake uploader/downloader, and a couple of fake dirnodes, then
|
# create a fake uploader/downloader, and a couple of fake dirnodes, then
|
||||||
# create a webserver that works against them
|
# create a webserver that works against them
|
||||||
|
|
||||||
@ -111,17 +113,6 @@ class FakeUploader(service.Service):
|
|||||||
return (self.helper_furl, self.helper_connected)
|
return (self.helper_furl, self.helper_connected)
|
||||||
|
|
||||||
|
|
||||||
class FakeStatus(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.status = []
|
|
||||||
|
|
||||||
def setServiceParent(self, p):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_status(self):
|
|
||||||
return self.status
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_queued_item(relpath_u, history=[]):
|
def create_test_queued_item(relpath_u, history=[]):
|
||||||
progress = mock.Mock()
|
progress = mock.Mock()
|
||||||
progress.progress = 100.0
|
progress.progress = 100.0
|
||||||
|
@ -958,23 +958,7 @@ class MapupdateStatusPage(rend.Page, RateAndTimeMixin):
|
|||||||
return T.li["Per-Server Response Times: ", l]
|
return T.li["Per-Server Response Times: ", l]
|
||||||
|
|
||||||
|
|
||||||
|
def marshal_json(s):
|
||||||
class Status(MultiFormatPage):
|
|
||||||
docFactory = getxmlfile("status.xhtml")
|
|
||||||
addSlash = True
|
|
||||||
|
|
||||||
def __init__(self, history):
|
|
||||||
rend.Page.__init__(self, history)
|
|
||||||
self.history = history
|
|
||||||
|
|
||||||
def render_JSON(self, req):
|
|
||||||
# modern browsers now render this instead of forcing downloads
|
|
||||||
req.setHeader("content-type", "application/json")
|
|
||||||
data = {}
|
|
||||||
data["active"] = active = []
|
|
||||||
data["recent"] = recent = []
|
|
||||||
|
|
||||||
def _marshal_json(s):
|
|
||||||
# common item data
|
# common item data
|
||||||
item = {
|
item = {
|
||||||
"storage-index-string": base32.b2a_or_none(s.get_storage_index()),
|
"storage-index-string": base32.b2a_or_none(s.get_storage_index()),
|
||||||
@ -1010,11 +994,27 @@ class Status(MultiFormatPage):
|
|||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
class Status(MultiFormatPage):
|
||||||
|
docFactory = getxmlfile("status.xhtml")
|
||||||
|
addSlash = True
|
||||||
|
|
||||||
|
def __init__(self, history):
|
||||||
|
rend.Page.__init__(self, history)
|
||||||
|
self.history = history
|
||||||
|
|
||||||
|
def render_JSON(self, req):
|
||||||
|
# modern browsers now render this instead of forcing downloads
|
||||||
|
req.setHeader("content-type", "application/json")
|
||||||
|
data = {}
|
||||||
|
data["active"] = active = []
|
||||||
|
data["recent"] = recent = []
|
||||||
|
|
||||||
for s in self._get_active_operations():
|
for s in self._get_active_operations():
|
||||||
active.append(_marshal_json(s))
|
active.append(marshal_json(s))
|
||||||
|
|
||||||
for s in self._get_recent_operations():
|
for s in self._get_recent_operations():
|
||||||
recent.append(_marshal_json(s))
|
recent.append(marshal_json(s))
|
||||||
|
|
||||||
return json.dumps(data, indent=1) + "\n"
|
return json.dumps(data, indent=1) + "\n"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user