From d142ccb159c70da4b1c1e4159f955f06b5deb7c2 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 4 Feb 2020 06:46:08 -0500 Subject: [PATCH 0001/1054] Use twisted.web.template in web/storage.py Related to ticket:3247. Nevow usage has been removed, and generated page looks the same as its former self, but tests are failing because test_storage.py assumes that we're using nevow. --- src/allmydata/web/storage.py | 258 ++++++++++++++----------- src/allmydata/web/storage_status.xhtml | 60 +++--- 2 files changed, 182 insertions(+), 136 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 79c0a8c38..3000fb70d 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -1,10 +1,10 @@ import time, json -from nevow import rend, tags as T +from twisted.python.filepath import FilePath +from twisted.web.template import tags as T, renderer, Element, renderElement, XMLFile from allmydata.web.common import ( - getxmlfile, abbreviate_time, - MultiFormatPage, + MultiFormatResource ) from allmydata.util.abbreviate import abbreviate_space from allmydata.util import time_format, idlib @@ -16,91 +16,100 @@ def remove_prefix(s, prefix): return s[len(prefix):] -class StorageStatus(MultiFormatPage): - docFactory = getxmlfile("storage_status.xhtml") - # the default 'data' argument is the StorageServer instance +class StorageStatusElement(Element): + loader = XMLFile(FilePath(__file__).sibling("storage_status.xhtml")) - def __init__(self, storage, nickname=""): - rend.Page.__init__(self, storage) + def __init__(self, storage, nickname): + super(StorageStatusElement, self).__init__() self.storage = storage - self.nickname = nickname + self.nick = nickname - def render_JSON(self, req): - req.setHeader("content-type", "text/plain") - d = {"stats": self.storage.get_stats(), - "bucket-counter": self.storage.bucket_counter.get_state(), - "lease-checker": self.storage.lease_checker.get_state(), - "lease-checker-progress": self.storage.lease_checker.get_progress(), - } - return json.dumps(d, indent=1) + "\n" + @renderer + def nickname(self, req, tag): + return self.nick - def data_nickname(self, ctx, storage): - return self.nickname - def data_nodeid(self, ctx, storage): + @renderer + def nodeid(self, req, tag): return idlib.nodeid_b2a(self.storage.my_nodeid) - def render_storage_running(self, ctx, storage): - if storage: - return ctx.tag - else: - return T.h1["No Storage Server Running"] + def get_stat(self, key): + return self.storage.get_stats().get(key) - def render_bool(self, ctx, data): - return {True: "Yes", False: "No"}[bool(data)] + def str(self, tag, val): + if val is None: + return tag("?") + return tag(str(val)) - def render_abbrev_space(self, ctx, size): - if size is None: - return "?" - return abbreviate_space(size) + def abbr(self, tag, val): + if val is None: + return tag("?") + return tag(abbreviate_space(val)) - def render_space(self, ctx, size): - if size is None: - return "?" - return "%d" % size + @renderer + def disk_total(self, req, tag): + return self.str(tag, self.get_stat("storage_server.disk_total")) - 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. + @renderer + def disk_total_abbrev(self, req, tag): + return self.abbr(tag, self.get_stat("storage_server.disk_total")) - # Nevow has nevow.accessors.DictionaryContainer: Any data= directive - # that appears in a context in which the current data is a dictionary - # will be looked up as keys in that dictionary. So if data_stats() - # returns a dictionary, then we can use something like this: - # - # + @renderer + def disk_used(self, req, tag): + return self.str(tag, self.get_stat("storage_server.disk_used")) - # to use get_stats()["storage_server.disk_total"] . However, - # DictionaryContainer does a raw d[] instead of d.get(), so any - # missing keys will cause an error, even if the renderer can tolerate - # None values. To overcome this, we either need a dict-like object - # that always returns None for unknown keys, or we must pre-populate - # our dict with those missing keys, or we should get rid of data_ - # methods that return dicts (or find some way to override Nevow's - # handling of dictionaries). + @renderer + def disk_used_abbrev(self, req, tag): + return self.abbr(tag, self.get_stat("storage_server.disk_used")) - d = dict([ (remove_prefix(k, "storage_server."), v) - for k,v in self.storage.get_stats().items() ]) - d.setdefault("disk_total", None) - d.setdefault("disk_used", None) - d.setdefault("disk_free_for_root", None) - d.setdefault("disk_free_for_nonroot", None) - d.setdefault("reserved_space", None) - d.setdefault("disk_avail", None) - return d + @renderer + def disk_free_for_root(self, req, tag): + return self.str(tag, self.get_stat("storage_server.disk_free_for_root")) - def data_last_complete_bucket_count(self, ctx, data): + @renderer + def disk_free_for_root_abbrev(self, req, tag): + return self.abbr(tag, self.get_stat("storage_server.disk_free_for_root")) + + @renderer + def disk_free_for_nonroot(self, req, tag): + return self.str(tag, self.get_stat("storage_server.disk_free_for_nonroot")) + + @renderer + def disk_free_for_nonroot_abbrev(self, req, tag): + return self.abbr(tag, self.get_stat("storage_server.disk_free_for_nonroot")) + + @renderer + def reserved_space(self, req, tag): + return self.str(tag, self.get_stat("storage_server.reserved_space")) + + @renderer + def reserved_space_abbrev(self, req, tag): + return self.abbr(tag, self.get_stat("storage_server.reserved_space")) + + @renderer + def disk_avail(self, req, tag): + return self.str(tag, self.get_stat("storage_server.disk_avail")) + + @renderer + def disk_avail_abbrev(self, req, tag): + return self.abbr(tag, self.get_stat("storage_server.disk_avail")) + + @renderer + def accepting_immutable_shares(self, req, tag): + accepting = self.get_stat("storage_server.accepting_immutable_shares") + return {True: "Yes", False: "No"}[bool(accepting)] + + @renderer + def last_complete_bucket_count(self, req, tag): s = self.storage.bucket_counter.get_state() count = s.get("last-complete-bucket-count") if count is None: return "Not computed yet" - return count + return str(count) - def render_count_crawler_status(self, ctx, storage): + @renderer + def count_crawler_status(self, req, tag): p = self.storage.bucket_counter.get_progress() - return ctx.tag[self.format_crawler_progress(p)] + return self.format_crawler_progress(p) def format_crawler_progress(self, p): cycletime = p["estimated-time-per-cycle"] @@ -127,55 +136,51 @@ class StorageStatus(MultiFormatPage): return ["Next crawl in %s" % abbreviate_time(soon), cycletime_s] - def render_lease_expiration_enabled(self, ctx, data): + @renderer + def storage_running(self, req, tag): + if self.storage: + return tag + return tag("No Storage Server Running") + + @renderer + def lease_expiration_enabled(self, req, tag): lc = self.storage.lease_checker if lc.expiration_enabled: - return ctx.tag["Enabled: expired leases will be removed"] + return tag("Enabled: expired leases will be removed") else: - return ctx.tag["Disabled: scan-only mode, no leases will be removed"] + return tag("Disabled: scan-only mode, no leases will be removed") - def render_lease_expiration_mode(self, ctx, data): + @renderer + def lease_expiration_mode(self, req, tag): lc = self.storage.lease_checker if lc.mode == "age": if lc.override_lease_duration is None: - ctx.tag["Leases will expire naturally, probably 31 days after " - "creation or renewal."] + tag("Leases will expire naturally, probably 31 days after " + "creation or renewal.") else: - ctx.tag["Leases created or last renewed more than %s ago " - "will be considered expired." - % abbreviate_time(lc.override_lease_duration)] + tag("Leases created or last renewed more than %s ago " + "will be considered expired." + % abbreviate_time(lc.override_lease_duration)) else: assert lc.mode == "cutoff-date" localizedutcdate = time.strftime("%d-%b-%Y", time.gmtime(lc.cutoff_date)) isoutcdate = time_format.iso_utc_date(lc.cutoff_date) - ctx.tag["Leases created or last renewed before %s (%s) UTC " - "will be considered expired." % (isoutcdate, localizedutcdate, )] + tag("Leases created or last renewed before %s (%s) UTC " + "will be considered expired." + % (isoutcdate, localizedutcdate, )) if len(lc.mode) > 2: - ctx.tag[" The following sharetypes will be expired: ", - " ".join(sorted(lc.sharetypes_to_expire)), "."] - return ctx.tag + tag(" The following sharetypes will be expired: ", + " ".join(sorted(lc.sharetypes_to_expire)), ".") + return tag - def format_recovered(self, sr, a): - def maybe(d): - if d is None: - return "?" - return "%d" % d - return "%s shares, %s buckets (%s mutable / %s immutable), %s (%s / %s)" % \ - (maybe(sr["%s-shares" % a]), - maybe(sr["%s-buckets" % a]), - maybe(sr["%s-buckets-mutable" % a]), - maybe(sr["%s-buckets-immutable" % a]), - abbreviate_space(sr["%s-diskbytes" % a]), - abbreviate_space(sr["%s-diskbytes-mutable" % a]), - abbreviate_space(sr["%s-diskbytes-immutable" % a]), - ) - - def render_lease_current_cycle_progress(self, ctx, data): + @renderer + def lease_current_cycle_progress(self, req, tag): lc = self.storage.lease_checker p = lc.get_progress() - return ctx.tag[self.format_crawler_progress(p)] + return tag(self.format_crawler_progress(p)) - def render_lease_current_cycle_results(self, ctx, data): + @renderer + def lease_current_cycle_results(self, req, tag): lc = self.storage.lease_checker p = lc.get_progress() if not p["cycle-in-progress"]: @@ -229,10 +234,10 @@ class StorageStatus(MultiFormatPage): T.ul[ [T.li[ ["SI %s shnum %d" % corrupt_share for corrupt_share in so_far["corrupt-shares"] ] ]]]) + return tag("Current cycle:", p) - return ctx.tag["Current cycle:", p] - - def render_lease_last_cycle_results(self, ctx, data): + @renderer + def lease_last_cycle_results(self, req, tag): lc = self.storage.lease_checker h = lc.get_state()["history"] if not h: @@ -240,15 +245,15 @@ class StorageStatus(MultiFormatPage): last = h[max(h.keys())] start, end = last["cycle-start-finish-times"] - ctx.tag["Last complete cycle (which took %s and finished %s ago)" - " recovered: " % (abbreviate_time(end-start), - abbreviate_time(time.time() - end)), - self.format_recovered(last["space-recovered"], "actual") - ] + tag("Last complete cycle (which took %s and finished %s ago)" + " recovered: " % (abbreviate_time(end-start), + abbreviate_time(time.time() - end)), + self.format_recovered(last["space-recovered"], "actual")) p = T.ul() + def add(*pieces): - p[T.li[pieces]] + p(T.li(pieces)) saw = self.format_recovered(last["space-recovered"], "examined") add("and saw a total of ", saw) @@ -264,4 +269,37 @@ class StorageStatus(MultiFormatPage): for corrupt_share in last["corrupt-shares"] ] ]]]) - return ctx.tag[p] + return tag(p) + + def format_recovered(self, sr, a): + def maybe(d): + if d is None: + return "?" + return "%d" % d + return "%s shares, %s buckets (%s mutable / %s immutable), %s (%s / %s)" % \ + (maybe(sr["%s-shares" % a]), + maybe(sr["%s-buckets" % a]), + maybe(sr["%s-buckets-mutable" % a]), + maybe(sr["%s-buckets-immutable" % a]), + abbreviate_space(sr["%s-diskbytes" % a]), + abbreviate_space(sr["%s-diskbytes-mutable" % a]), + abbreviate_space(sr["%s-diskbytes-immutable" % a]), + ) + +class StorageStatus(MultiFormatResource): + def __init__(self, storage, nickname=""): + super(StorageStatus, self).__init__() + self.storage = storage + self.nickname = nickname + + def render_HTML(self, req): + return renderElement(req, StorageStatusElement(self.storage, self.nickname)) + + def render_JSON(self, req): + req.setHeader("content-type", "text/plain") + d = {"stats": self.storage.get_stats(), + "bucket-counter": self.storage.bucket_counter.get_state(), + "lease-checker": self.storage.lease_checker.get_state(), + "lease-checker-progress": self.storage.lease_checker.get_progress(), + } + return json.dumps(d, indent=1) + "\n" diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml index d97daf9af..cfd7a860c 100644 --- a/src/allmydata/web/storage_status.xhtml +++ b/src/allmydata/web/storage_status.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - Storage Server Status @@ -7,19 +7,19 @@ -
+

Storage Server Status

- +
- - + + - - + + - - + + - - + + - - + + - - + +
Total disk space:()()
Disk space used:- ()- ()
@@ -28,18 +28,18 @@
Disk space free (root):()() [see 1]
Disk space free (non-root):()() [see 2]
Reserved space:- ()- ()
@@ -48,23 +48,31 @@
Space Available to Tahoe:()()
    -
  • Server Nickname:
  • -
  • Server Nodeid:
  • -
  • Accepting new shares: -
  • +
  • Server Nickname: + + + +
  • +
  • Server Nodeid: + + + +
  • +
  • Accepting new shares: +
  • Total buckets: - + (the number of files and directories for which this server is holding a share)
      -
    • +
@@ -72,11 +80,11 @@

Lease Expiration Crawler

    -
  • Expiration
  • -
  • -
  • -
  • -
  • +
  • Expiration
  • +
  • +
  • +
  • +

From b29652e0f0d27aafd52771b3f7b57f511fcec076 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 4 Feb 2020 22:59:45 -0500 Subject: [PATCH 0002/1054] Add `StorageStatus::renderSynchronously` Related to ticket:3247 test_storage.py wants a `StorageStatus::renderSynchronously()` method and a `StorageStatus::renderHTTP()` method. Let us begin with the goofy first-cut. Both these methods are not only wrong, but they will also not please the test suite. However error messages produced in CI can be shared, and that way I can hopefully get unstuck. --- src/allmydata/web/storage.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 3000fb70d..8f36cf44d 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -1,7 +1,8 @@ import time, json from twisted.python.filepath import FilePath -from twisted.web.template import tags as T, renderer, Element, renderElement, XMLFile +from twisted.web.template import tags as T, \ + renderer, Element, renderElement, XMLFile from allmydata.web.common import ( abbreviate_time, MultiFormatResource @@ -303,3 +304,14 @@ class StorageStatus(MultiFormatResource): "lease-checker-progress": self.storage.lease_checker.get_progress(), } return json.dumps(d, indent=1) + "\n" + + def renderSynchronously(self): + # to appease the test suite. + elem = StorageStatusElement(self.storage, self.nickname) + result = [] + flattenString(None, elem).addCallback(result.append) + return result + + def renderHTTP(self, ctx=None): + # to appease the test suite. + self.renderSynchronously() From d3790a4d4227f31f06a35042448a1a119fa4658e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 4 Feb 2020 23:16:48 -0500 Subject: [PATCH 0003/1054] Add missing `flattenString` import --- src/allmydata/web/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 8f36cf44d..a912d79f6 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -2,7 +2,7 @@ import time, json from twisted.python.filepath import FilePath from twisted.web.template import tags as T, \ - renderer, Element, renderElement, XMLFile + renderer, Element, renderElement, XMLFile, flattenString from allmydata.web.common import ( abbreviate_time, MultiFormatResource From c019c7e9556e31c599997ce6463fe679668c3a73 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 5 Feb 2020 22:05:33 -0500 Subject: [PATCH 0004/1054] Second version of renderSynchronously --- src/allmydata/web/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index a912d79f6..4ad5a2baf 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -310,7 +310,7 @@ class StorageStatus(MultiFormatResource): elem = StorageStatusElement(self.storage, self.nickname) result = [] flattenString(None, elem).addCallback(result.append) - return result + return result[0] def renderHTTP(self, ctx=None): # to appease the test suite. From 4e81a3a0a2906a2e2c3774a126afa750b7f9bf3c Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 5 Feb 2020 22:09:16 -0500 Subject: [PATCH 0005/1054] Check storage server status before using it --- src/allmydata/web/storage.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 4ad5a2baf..9a99286bf 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -31,9 +31,13 @@ class StorageStatusElement(Element): @renderer def nodeid(self, req, tag): + if not self.storage: + return tag("No storage server running.") return idlib.nodeid_b2a(self.storage.my_nodeid) def get_stat(self, key): + if not self.storage: + return None return self.storage.get_stats().get(key) def str(self, tag, val): @@ -101,6 +105,8 @@ class StorageStatusElement(Element): @renderer def last_complete_bucket_count(self, req, tag): + if not self.storage: + return tag("No storage server running.") s = self.storage.bucket_counter.get_state() count = s.get("last-complete-bucket-count") if count is None: @@ -109,6 +115,8 @@ class StorageStatusElement(Element): @renderer def count_crawler_status(self, req, tag): + if not self.storage: + return tag("No storage server running.") p = self.storage.bucket_counter.get_progress() return self.format_crawler_progress(p) @@ -145,6 +153,8 @@ class StorageStatusElement(Element): @renderer def lease_expiration_enabled(self, req, tag): + if not self.storage: + return tag("No storage server running.") lc = self.storage.lease_checker if lc.expiration_enabled: return tag("Enabled: expired leases will be removed") @@ -153,6 +163,8 @@ class StorageStatusElement(Element): @renderer def lease_expiration_mode(self, req, tag): + if not self.storage: + return tag("No storage server running.") lc = self.storage.lease_checker if lc.mode == "age": if lc.override_lease_duration is None: @@ -176,12 +188,16 @@ class StorageStatusElement(Element): @renderer def lease_current_cycle_progress(self, req, tag): + if not self.storage: + return tag("No storage server running.") lc = self.storage.lease_checker p = lc.get_progress() return tag(self.format_crawler_progress(p)) @renderer def lease_current_cycle_results(self, req, tag): + if not self.storage: + return tag("No storage server running.") lc = self.storage.lease_checker p = lc.get_progress() if not p["cycle-in-progress"]: @@ -239,6 +255,8 @@ class StorageStatusElement(Element): @renderer def lease_last_cycle_results(self, req, tag): + if not self.storage: + return tag("No storage server running.") lc = self.storage.lease_checker h = lc.get_state()["history"] if not h: From c88c97aad540b928129944bdcd349df6c7df72de Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 5 Feb 2020 23:09:39 -0500 Subject: [PATCH 0006/1054] Use right syntax for twisted.web.template tags --- src/allmydata/web/storage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 9a99286bf..1e206d8cf 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -212,7 +212,7 @@ class StorageStatusElement(Element): p = T.ul() def add(*pieces): - p[T.li[pieces]] + p(T.li(pieces)) def maybe(d): if d is None: @@ -248,9 +248,9 @@ class StorageStatusElement(Element): if so_far["corrupt-shares"]: add("Corrupt shares:", - T.ul[ [T.li[ ["SI %s shnum %d" % corrupt_share + T.ul( (T.li( ["SI %s shnum %d" % corrupt_share for corrupt_share in so_far["corrupt-shares"] ] - ]]]) + )))) return tag("Current cycle:", p) @renderer @@ -284,9 +284,9 @@ class StorageStatusElement(Element): if last["corrupt-shares"]: add("Corrupt shares:", - T.ul[ [T.li[ ["SI %s shnum %d" % corrupt_share + T.ul( (T.li( ["SI %s shnum %d" % corrupt_share for corrupt_share in last["corrupt-shares"] ] - ]]]) + )))) return tag(p) From c7a63f957d4a476092f3bcff70e57dacc9d47c60 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 05:17:35 -0500 Subject: [PATCH 0007/1054] Refactor so that test_util pass --- src/allmydata/test/test_storage.py | 5 +++-- src/allmydata/web/storage.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 9f3aee9b8..85f4dd2bd 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -30,7 +30,8 @@ from allmydata.interfaces import BadWriteEnablerError from allmydata.test.common import LoggingServiceParent, ShouldFailMixin from allmydata.test.common_web import WebRenderingMixin from allmydata.test.no_network import NoNetworkServer -from allmydata.web.storage import StorageStatus, remove_prefix +from allmydata.web.storage import StorageStatus, StorageStatusElement, \ + remove_prefix from allmydata.storage_client import ( _StorageServer, ) @@ -4208,7 +4209,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failUnlessIn("Reserved space: - 10.00 MB (10000000)", s) def test_util(self): - w = StorageStatus(None) + w = StorageStatusElement(None, None) self.failUnlessEqual(w.render_space(None, None), "?") self.failUnlessEqual(w.render_space(None, 10e6), "10000000") self.failUnlessEqual(w.render_abbrev_space(None, None), "?") diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 1e206d8cf..6b033bc5a 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -50,6 +50,16 @@ class StorageStatusElement(Element): return tag("?") return tag(abbreviate_space(val)) + def render_abbrev_space(self, ctx, size): + if size is None: + return "?" + return abbreviate_space(size) + + def render_space(self, ctx, size): + if size is None: + return "?" + return "%d" % size + @renderer def disk_total(self, req, tag): return self.str(tag, self.get_stat("storage_server.disk_total")) From 0993e610468afe97876c9e4af4367ac9a6a7bde4 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 05:20:45 -0500 Subject: [PATCH 0008/1054] Drop unused `ctx` argument from render_space methods --- src/allmydata/test/test_storage.py | 8 ++++---- src/allmydata/web/storage.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 85f4dd2bd..6f4c455f2 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -4210,9 +4210,9 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): def test_util(self): w = StorageStatusElement(None, None) - self.failUnlessEqual(w.render_space(None, None), "?") - self.failUnlessEqual(w.render_space(None, 10e6), "10000000") - self.failUnlessEqual(w.render_abbrev_space(None, None), "?") - self.failUnlessEqual(w.render_abbrev_space(None, 10e6), "10.00 MB") + self.failUnlessEqual(w.render_space(None), "?") + self.failUnlessEqual(w.render_space(10e6), "10000000") + self.failUnlessEqual(w.render_abbrev_space(None), "?") + self.failUnlessEqual(w.render_abbrev_space(10e6), "10.00 MB") self.failUnlessEqual(remove_prefix("foo.bar", "foo."), "bar") self.failUnlessEqual(remove_prefix("foo.bar", "baz."), None) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 6b033bc5a..88f099b24 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -50,12 +50,12 @@ class StorageStatusElement(Element): return tag("?") return tag(abbreviate_space(val)) - def render_abbrev_space(self, ctx, size): + def render_abbrev_space(self, size): if size is None: return "?" return abbreviate_space(size) - def render_space(self, ctx, size): + def render_space(self, size): if size is None: return "?" return "%d" % size From d46df30bd059633d68dee3eb5d42ba5e375d0d5c Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 05:49:11 -0500 Subject: [PATCH 0009/1054] Use render_space methods to render space --- src/allmydata/web/storage.py | 46 +++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 88f099b24..496766b92 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -40,16 +40,6 @@ class StorageStatusElement(Element): return None return self.storage.get_stats().get(key) - def str(self, tag, val): - if val is None: - return tag("?") - return tag(str(val)) - - def abbr(self, tag, val): - if val is None: - return tag("?") - return tag(abbreviate_space(val)) - def render_abbrev_space(self, size): if size is None: return "?" @@ -62,51 +52,63 @@ class StorageStatusElement(Element): @renderer def disk_total(self, req, tag): - return self.str(tag, self.get_stat("storage_server.disk_total")) + val = self.get_stat("storage_server.disk_total") + return tag(self.render_space(val)) @renderer def disk_total_abbrev(self, req, tag): - return self.abbr(tag, self.get_stat("storage_server.disk_total")) + val = self.get_stat("storage_server.disk_total") + return tag(self.render_abbrev_space(val)) @renderer def disk_used(self, req, tag): - return self.str(tag, self.get_stat("storage_server.disk_used")) + val = self.get_stat("storage_server.disk_used") + return tag(self.render_space(val)) @renderer def disk_used_abbrev(self, req, tag): - return self.abbr(tag, self.get_stat("storage_server.disk_used")) + val = self.get_stat("storage_server.disk_used") + return tag(self.render_abbrev_space(val)) @renderer def disk_free_for_root(self, req, tag): - return self.str(tag, self.get_stat("storage_server.disk_free_for_root")) + val = self.get_stat("storage_server.disk_free_for_root") + return tag(self.render_space(val)) @renderer def disk_free_for_root_abbrev(self, req, tag): - return self.abbr(tag, self.get_stat("storage_server.disk_free_for_root")) + val = self.get_stat("storage_server.disk_free_for_root") + return tag(self.render_abbrev_space(val)) @renderer def disk_free_for_nonroot(self, req, tag): - return self.str(tag, self.get_stat("storage_server.disk_free_for_nonroot")) + val = self.get_stat("storage_server.disk_free_for_nonroot") + return tag(self.render_space(val)) @renderer def disk_free_for_nonroot_abbrev(self, req, tag): - return self.abbr(tag, self.get_stat("storage_server.disk_free_for_nonroot")) + val = self.get_stat("storage_server.disk_free_for_nonroot") + return tag(self.render_abbrev_space(val)) @renderer def reserved_space(self, req, tag): - return self.str(tag, self.get_stat("storage_server.reserved_space")) + val = self.get_stat("storage_server.reserved_space") + return tag(self.render_space(val)) @renderer def reserved_space_abbrev(self, req, tag): - return self.abbr(tag, self.get_stat("storage_server.reserved_space")) + val = self.get_stat("storage_server.reserved_space") + return tag(self.render_abbrev_space(val)) @renderer def disk_avail(self, req, tag): - return self.str(tag, self.get_stat("storage_server.disk_avail")) + val = self.get_stat("storage_server.disk_avail") + return tag(self.render_space(val)) @renderer def disk_avail_abbrev(self, req, tag): - return self.abbr(tag, self.get_stat("storage_server.disk_avail")) + val = self.get_stat("storage_server.disk_avail") + return tag(self.render_abbrev_space(val)) @renderer def accepting_immutable_shares(self, req, tag): From b14f36082c148119cc9814c56a02a4155447032a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 06:22:25 -0500 Subject: [PATCH 0010/1054] Use within table cells --- src/allmydata/web/storage_status.xhtml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml index cfd7a860c..6e0e26008 100644 --- a/src/allmydata/web/storage_status.xhtml +++ b/src/allmydata/web/storage_status.xhtml @@ -13,13 +13,13 @@ - - + + - - + + - - + + - - + + - - + + - - + +
Total disk space:()()
Disk space used:- ()- ()
@@ -28,18 +28,18 @@
Disk space free (root):()() [see 1]
Disk space free (non-root):()() [see 2]
Reserved space:- ()- ()
@@ -48,8 +48,8 @@
Space Available to Tahoe:()()
From 2df2ae92d56722ee9d810a678d9b74fe197cb1ab Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 07:34:55 -0500 Subject: [PATCH 0011/1054] Fix test_storage.WebStatus.test_no_server failure --- src/allmydata/web/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 496766b92..c2e517dce 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -161,7 +161,7 @@ class StorageStatusElement(Element): def storage_running(self, req, tag): if self.storage: return tag - return tag("No Storage Server Running") + return T.h1("No Storage Server Running") @renderer def lease_expiration_enabled(self, req, tag): From b1c78244abeab83e0061e7586b26d4b44911a85d Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 08:19:29 -0500 Subject: [PATCH 0012/1054] Use parentheses in import statement --- src/allmydata/web/storage.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index c2e517dce..d45541457 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -1,8 +1,14 @@ import time, json from twisted.python.filepath import FilePath -from twisted.web.template import tags as T, \ - renderer, Element, renderElement, XMLFile, flattenString +from twisted.web.template import ( + Element, + XMLFile, + tags as T, + renderer, + renderElement, + flattenString +) from allmydata.web.common import ( abbreviate_time, MultiFormatResource From d3ff578640f85181fffd787b5209e7847033d8e9 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 09:55:33 -0500 Subject: [PATCH 0013/1054] Use parentheses in test suite's import statement --- src/allmydata/test/test_storage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 6f4c455f2..6956fc757 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -30,8 +30,11 @@ from allmydata.interfaces import BadWriteEnablerError from allmydata.test.common import LoggingServiceParent, ShouldFailMixin from allmydata.test.common_web import WebRenderingMixin from allmydata.test.no_network import NoNetworkServer -from allmydata.web.storage import StorageStatus, StorageStatusElement, \ +from allmydata.web.storage import ( + StorageStatus, + StorageStatusElement, remove_prefix +) from allmydata.storage_client import ( _StorageServer, ) From 227d06fe64533ba45b1319b7f498e0df3492f670 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 11 Feb 2020 17:02:14 -0500 Subject: [PATCH 0014/1054] Add docstrings to StorageStatusElement --- src/allmydata/web/storage.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index d45541457..df8354a38 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -24,9 +24,15 @@ def remove_prefix(s, prefix): class StorageStatusElement(Element): + """Class to render a storage status page.""" + loader = XMLFile(FilePath(__file__).sibling("storage_status.xhtml")) - def __init__(self, storage, nickname): + def __init__(self, storage, nickname=""): + """ + :param _StorageServer storage: data about storage. + :param string nickname: friendly name for storage. + """ super(StorageStatusElement, self).__init__() self.storage = storage self.nick = nickname From f22417e51b4fadbba09cf2ad882605c5aecaef20 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 12 Feb 2020 12:14:24 -0500 Subject: [PATCH 0015/1054] Rename function for clarity --- src/allmydata/web/storage.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index df8354a38..a5d65fa85 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -47,7 +47,7 @@ class StorageStatusElement(Element): return tag("No storage server running.") return idlib.nodeid_b2a(self.storage.my_nodeid) - def get_stat(self, key): + def _get_storage_stat(self, key): if not self.storage: return None return self.storage.get_stats().get(key) @@ -64,67 +64,67 @@ class StorageStatusElement(Element): @renderer def disk_total(self, req, tag): - val = self.get_stat("storage_server.disk_total") + val = self._get_storage_stat("storage_server.disk_total") return tag(self.render_space(val)) @renderer def disk_total_abbrev(self, req, tag): - val = self.get_stat("storage_server.disk_total") + val = self._get_storage_stat("storage_server.disk_total") return tag(self.render_abbrev_space(val)) @renderer def disk_used(self, req, tag): - val = self.get_stat("storage_server.disk_used") + val = self._get_storage_stat("storage_server.disk_used") return tag(self.render_space(val)) @renderer def disk_used_abbrev(self, req, tag): - val = self.get_stat("storage_server.disk_used") + val = self._get_storage_stat("storage_server.disk_used") return tag(self.render_abbrev_space(val)) @renderer def disk_free_for_root(self, req, tag): - val = self.get_stat("storage_server.disk_free_for_root") + val = self._get_storage_stat("storage_server.disk_free_for_root") return tag(self.render_space(val)) @renderer def disk_free_for_root_abbrev(self, req, tag): - val = self.get_stat("storage_server.disk_free_for_root") + val = self._get_storage_stat("storage_server.disk_free_for_root") return tag(self.render_abbrev_space(val)) @renderer def disk_free_for_nonroot(self, req, tag): - val = self.get_stat("storage_server.disk_free_for_nonroot") + val = self._get_storage_stat("storage_server.disk_free_for_nonroot") return tag(self.render_space(val)) @renderer def disk_free_for_nonroot_abbrev(self, req, tag): - val = self.get_stat("storage_server.disk_free_for_nonroot") + val = self._get_storage_stat("storage_server.disk_free_for_nonroot") return tag(self.render_abbrev_space(val)) @renderer def reserved_space(self, req, tag): - val = self.get_stat("storage_server.reserved_space") + val = self._get_storage_stat("storage_server.reserved_space") return tag(self.render_space(val)) @renderer def reserved_space_abbrev(self, req, tag): - val = self.get_stat("storage_server.reserved_space") + val = self._get_storage_stat("storage_server.reserved_space") return tag(self.render_abbrev_space(val)) @renderer def disk_avail(self, req, tag): - val = self.get_stat("storage_server.disk_avail") + val = self._get_storage_stat("storage_server.disk_avail") return tag(self.render_space(val)) @renderer def disk_avail_abbrev(self, req, tag): - val = self.get_stat("storage_server.disk_avail") + val = self._get_storage_stat("storage_server.disk_avail") return tag(self.render_abbrev_space(val)) @renderer def accepting_immutable_shares(self, req, tag): - accepting = self.get_stat("storage_server.accepting_immutable_shares") + accepting = self._get_storage_stat("storage_server.accepting_immutable_shares") return {True: "Yes", False: "No"}[bool(accepting)] @renderer From 6e9a4e30d733c2a08d801df03f1107d9177ec76e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 12 Feb 2020 12:15:26 -0500 Subject: [PATCH 0016/1054] Add a docstring --- src/allmydata/web/storage.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index a5d65fa85..263fa9344 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -48,6 +48,30 @@ class StorageStatusElement(Element): return idlib.nodeid_b2a(self.storage.my_nodeid) def _get_storage_stat(self, key): + """Get storage server statistics. + + Storage Server keeps a dict that contains various usage and + latency statistics. The dict looks like this: + + { + 'storage_server.accepting_immutable_shares': 1, + 'storage_server.allocated': 0, + 'storage_server.disk_avail': 106539192320, + 'storage_server.disk_free_for_nonroot': 106539192320, + 'storage_server.disk_free_for_root': 154415284224, + 'storage_server.disk_total': 941088460800, + 'storage_server.disk_used': 786673176576, + 'storage_server.latencies.add-lease.01_0_percentile': None, + 'storage_server.latencies.add-lease.10_0_percentile': None, + ... + } + + ``StorageServer.get_stats()`` returns the above dict. Storage + status page uses a subset of the items in the dict, concerning + disk usage. + + :param str key: storage server statistic we want to know. + """ if not self.storage: return None return self.storage.get_stats().get(key) From 9b51bdf7faca3314e5dff2de22b8e035b9114fec Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 12:25:20 -0500 Subject: [PATCH 0017/1054] Return result from renderHTTP Bogus renderHTTP, but test failures are now down to three. We just need to handle requests for JSON now. --- src/allmydata/web/storage.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 263fa9344..1656df70b 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -378,6 +378,18 @@ class StorageStatus(MultiFormatResource): flattenString(None, elem).addCallback(result.append) return result[0] + # to appease the test suite def renderHTTP(self, ctx=None): - # to appease the test suite. - self.renderSynchronously() + """Send HTML or JSON formatted data, based on request. + + This function contains a bit of nevow-ism, but since this is + only called from the test suite, the nevow-ism should go away + as we update things. + + :param _nevow.context.WovenContext ctx: context is passed on + from the test suite. We get a request out of this + context, and use the request to render a result. + + """ + from nevow.inevow import IRequest + return self.render(IRequest(ctx)) From 7a053ddeff85d32376c12582ca35a825e708704a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 15:29:42 -0500 Subject: [PATCH 0018/1054] Use explicit `None`-check on self.storage --- src/allmydata/web/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 1656df70b..76a09fda3 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -43,7 +43,7 @@ class StorageStatusElement(Element): @renderer def nodeid(self, req, tag): - if not self.storage: + if self.storage is None: return tag("No storage server running.") return idlib.nodeid_b2a(self.storage.my_nodeid) From e2fc1fc07ea56cd6b4805864d1d275170e0c261e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 16:49:15 -0500 Subject: [PATCH 0019/1054] Move `renderSynchronously` to test suite --- src/allmydata/test/test_storage.py | 36 +++++++++++++++++++----------- src/allmydata/web/storage.py | 10 +-------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 6956fc757..f37401cc2 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -4,6 +4,7 @@ from twisted.trial import unittest from twisted.internet import defer from twisted.application import service +from twisted.web.template import flattenString from foolscap.api import fireEventually import itertools from allmydata import interfaces @@ -2963,6 +2964,15 @@ def remove_tags(s): s = re.sub(r'\s+', ' ', s) return s +def renderSynchronously(ss): + """ + :param _StorageStatus ss: a StorageStatus instance. + """ + elem = StorageStatusElement(ss.storage, ss.nickname) + result = [] + flattenString(None, elem).addCallback(result.append) + return result[0] + class MyBucketCountingCrawler(BucketCountingCrawler): def finished_prefix(self, cycle, prefix): BucketCountingCrawler.finished_prefix(self, cycle, prefix) @@ -2999,7 +3009,7 @@ class BucketCounter(unittest.TestCase, pollmixin.PollMixin): w = StorageStatus(ss) # this sample is before the crawler has started doing anything - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) self.failUnlessIn("Accepting new shares: Yes", s) @@ -3022,7 +3032,7 @@ class BucketCounter(unittest.TestCase, pollmixin.PollMixin): self.failUnlessEqual(state["last-complete-prefix"], ss.bucket_counter.prefixes[0]) ss.bucket_counter.cpu_slice = 100.0 # finish as fast as possible - html = w.renderSynchronously() + html = renderSynchronously(w) s = remove_tags(html) self.failUnlessIn(" Current crawl ", s) self.failUnlessIn(" (next work in ", s) @@ -3034,7 +3044,7 @@ class BucketCounter(unittest.TestCase, pollmixin.PollMixin): d.addCallback(lambda ignored: self.poll(_watch)) def _check2(ignored): ss.bucket_counter.cpu_slice = orig_cpu_slice - html = w.renderSynchronously() + html = renderSynchronously(w) s = remove_tags(html) self.failUnlessIn("Total buckets: 0 (the number of", s) self.failUnless("Next crawl in 59 minutes" in s or "Next crawl in 60 minutes" in s, s) @@ -3096,20 +3106,20 @@ class BucketCounter(unittest.TestCase, pollmixin.PollMixin): def _check_1(ignored): # no ETA is available yet - html = w.renderSynchronously() + html = renderSynchronously(w) s = remove_tags(html) self.failUnlessIn("complete (next work", s) def _check_2(ignored): # one prefix has finished, so an ETA based upon that elapsed time # should be available. - html = w.renderSynchronously() + html = renderSynchronously(w) s = remove_tags(html) self.failUnlessIn("complete (ETA ", s) def _check_3(ignored): # two prefixes have finished - html = w.renderSynchronously() + html = renderSynchronously(w) s = remove_tags(html) self.failUnlessIn("complete (ETA ", s) d.callback("done") @@ -4064,7 +4074,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): def test_no_server(self): w = StorageStatus(None) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

No Storage Server Running

", html) def test_status(self): @@ -4110,7 +4120,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss = StorageServer(basedir, "\x00" * 20) ss.setServiceParent(self.s) w = StorageStatus(ss) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) self.failUnlessIn("Accepting new shares: Yes", s) @@ -4130,7 +4140,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss = StorageServer(basedir, "\x00" * 20) ss.setServiceParent(self.s) w = StorageStatus(ss) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) self.failUnlessIn("Accepting new shares: No", s) @@ -4166,7 +4176,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss.setServiceParent(self.s) w = StorageStatus(ss) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) @@ -4184,7 +4194,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss = StorageServer(basedir, "\x00" * 20, readonly_storage=True) ss.setServiceParent(self.s) w = StorageStatus(ss) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) self.failUnlessIn("Accepting new shares: No", s) @@ -4195,7 +4205,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss = StorageServer(basedir, "\x00" * 20, reserved_space=10e6) ss.setServiceParent(self.s) w = StorageStatus(ss) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) self.failUnlessIn("Reserved space: - 10.00 MB (10000000)", s) @@ -4206,7 +4216,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss = StorageServer(basedir, "\x00" * 20, reserved_space=10e6) ss.setServiceParent(self.s) w = StorageStatus(ss) - html = w.renderSynchronously() + html = renderSynchronously(w) self.failUnlessIn("

Storage Server Status

", html) s = remove_tags(html) self.failUnlessIn("Reserved space: - 10.00 MB (10000000)", s) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 76a09fda3..e9c2f6bb5 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -6,8 +6,7 @@ from twisted.web.template import ( XMLFile, tags as T, renderer, - renderElement, - flattenString + renderElement ) from allmydata.web.common import ( abbreviate_time, @@ -371,13 +370,6 @@ class StorageStatus(MultiFormatResource): } return json.dumps(d, indent=1) + "\n" - def renderSynchronously(self): - # to appease the test suite. - elem = StorageStatusElement(self.storage, self.nickname) - result = [] - flattenString(None, elem).addCallback(result.append) - return result[0] - # to appease the test suite def renderHTTP(self, ctx=None): """Send HTML or JSON formatted data, based on request. From c061f6830e66ff7770bc0af43eb0442fbdf0f441 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 17:00:54 -0500 Subject: [PATCH 0020/1054] Use `successResultOf` in `renderSynchronously` Get rid of [].append trick when dealing with the deferred. --- src/allmydata/test/test_storage.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index f37401cc2..0a176bd60 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -2969,9 +2969,8 @@ def renderSynchronously(ss): :param _StorageStatus ss: a StorageStatus instance. """ elem = StorageStatusElement(ss.storage, ss.nickname) - result = [] - flattenString(None, elem).addCallback(result.append) - return result[0] + deferred = flattenString(None, elem) + return unittest.TestCase().successResultOf(deferred) class MyBucketCountingCrawler(BucketCountingCrawler): def finished_prefix(self, cycle, prefix): From aab940f65fb95815c9998b79876adeda5a0fb5f8 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 17:18:37 -0500 Subject: [PATCH 0021/1054] Remove redundant `None`-checks on `self.storage` When no storage is up, `storage_running()` renderer will return a big honking `no storage server running` message, and no further renderers will be invoked. Therefore the extra defense is probably not required. (I tested this hypothesis. The extra defense is not required, unless there's something I have not seen.) --- src/allmydata/web/storage.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index e9c2f6bb5..cbee2780f 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -42,8 +42,6 @@ class StorageStatusElement(Element): @renderer def nodeid(self, req, tag): - if self.storage is None: - return tag("No storage server running.") return idlib.nodeid_b2a(self.storage.my_nodeid) def _get_storage_stat(self, key): @@ -71,8 +69,6 @@ class StorageStatusElement(Element): :param str key: storage server statistic we want to know. """ - if not self.storage: - return None return self.storage.get_stats().get(key) def render_abbrev_space(self, size): @@ -152,8 +148,6 @@ class StorageStatusElement(Element): @renderer def last_complete_bucket_count(self, req, tag): - if not self.storage: - return tag("No storage server running.") s = self.storage.bucket_counter.get_state() count = s.get("last-complete-bucket-count") if count is None: @@ -162,8 +156,6 @@ class StorageStatusElement(Element): @renderer def count_crawler_status(self, req, tag): - if not self.storage: - return tag("No storage server running.") p = self.storage.bucket_counter.get_progress() return self.format_crawler_progress(p) @@ -200,8 +192,6 @@ class StorageStatusElement(Element): @renderer def lease_expiration_enabled(self, req, tag): - if not self.storage: - return tag("No storage server running.") lc = self.storage.lease_checker if lc.expiration_enabled: return tag("Enabled: expired leases will be removed") @@ -210,8 +200,6 @@ class StorageStatusElement(Element): @renderer def lease_expiration_mode(self, req, tag): - if not self.storage: - return tag("No storage server running.") lc = self.storage.lease_checker if lc.mode == "age": if lc.override_lease_duration is None: @@ -235,16 +223,12 @@ class StorageStatusElement(Element): @renderer def lease_current_cycle_progress(self, req, tag): - if not self.storage: - return tag("No storage server running.") lc = self.storage.lease_checker p = lc.get_progress() return tag(self.format_crawler_progress(p)) @renderer def lease_current_cycle_results(self, req, tag): - if not self.storage: - return tag("No storage server running.") lc = self.storage.lease_checker p = lc.get_progress() if not p["cycle-in-progress"]: @@ -302,8 +286,6 @@ class StorageStatusElement(Element): @renderer def lease_last_cycle_results(self, req, tag): - if not self.storage: - return tag("No storage server running.") lc = self.storage.lease_checker h = lc.get_state()["history"] if not h: From 0382b1ec63cfdcebf5f76f03ee939edbd0901d35 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 17:23:35 -0500 Subject: [PATCH 0022/1054] Use Unicode strings to render space --- src/allmydata/web/storage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index cbee2780f..607eeff79 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -73,13 +73,13 @@ class StorageStatusElement(Element): def render_abbrev_space(self, size): if size is None: - return "?" + return u"?" return abbreviate_space(size) def render_space(self, size): if size is None: - return "?" - return "%d" % size + return u"?" + return u"%d" % size @renderer def disk_total(self, req, tag): From 36a486426e970d86fb2dea42ab9ffba0dfc6238c Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 13 Feb 2020 22:02:30 -0500 Subject: [PATCH 0023/1054] Mark `format_recovered` as static method --- src/allmydata/web/storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 607eeff79..d670c4231 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -319,7 +319,8 @@ class StorageStatusElement(Element): return tag(p) - def format_recovered(self, sr, a): + @staticmethod + def format_recovered(sr, a): def maybe(d): if d is None: return "?" From 9bb7812148b7aa17cfa641f080ab8e3eb2bce642 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 14 Feb 2020 07:25:49 -0500 Subject: [PATCH 0024/1054] Add news fragment --- newsfragments/3247.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3247.minor diff --git a/newsfragments/3247.minor b/newsfragments/3247.minor new file mode 100644 index 000000000..e69de29bb From 7625d959bc2735816710af01e80073b102312e17 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 14 Feb 2020 08:26:44 -0500 Subject: [PATCH 0025/1054] Use to render node nickname and id CI did not like the old way, but it passed in my system. Odd. --- src/allmydata/web/storage_status.xhtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml index 6e0e26008..8f74c478c 100644 --- a/src/allmydata/web/storage_status.xhtml +++ b/src/allmydata/web/storage_status.xhtml @@ -57,12 +57,12 @@
  • Server Nickname: - +
  • Server Nodeid: - +
  • Accepting new shares: From ff019e5b12d3210046e269519a1e474f1d99ceb3 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 14 Feb 2020 10:10:48 -0500 Subject: [PATCH 0026/1054] Use BeautifulSoup to check favicon in storage page --- src/allmydata/test/web/test_web.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py index f84923521..19753d3b7 100644 --- a/src/allmydata/test/web/test_web.py +++ b/src/allmydata/test/web/test_web.py @@ -963,8 +963,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi def test_storage(self): d = self.GET("/storage") def _check(res): - self.failUnlessIn('Storage Server Status', res) - self.failUnlessIn(FAVICON_MARKUP, res) + soup = BeautifulSoup(res, 'html5lib') + assert_soup_has_text(self, soup, 'Storage Server Status') + assert_soup_has_favicon(self, soup) res_u = res.decode('utf-8') self.failUnlessIn(u'
  • Server Nickname: fake_nickname \u263A
  • ', res_u) d.addCallback(_check) From 0cbe2871fd80ffd186d54848995c6b6c91ee5ca1 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 14 Feb 2020 11:42:42 -0500 Subject: [PATCH 0027/1054] Give the suite the precise string it wants --- src/allmydata/web/storage_status.xhtml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml index 8f74c478c..d052fbabd 100644 --- a/src/allmydata/web/storage_status.xhtml +++ b/src/allmydata/web/storage_status.xhtml @@ -55,16 +55,8 @@
      -
    • Server Nickname: - - - -
    • -
    • Server Nodeid: - - - -
    • +
    • Server Nickname:
    • +
    • Server Nodeid:
    • Accepting new shares:
    • Total buckets: From 6c3256517a0fa8cd1b8c00267d0f80325f868a70 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 17 Feb 2020 12:52:48 -0500 Subject: [PATCH 0028/1054] Use slots to render storage stats table --- src/allmydata/web/storage.py | 80 ++++++++------------------ src/allmydata/web/storage_status.xhtml | 26 ++++----- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index d670c4231..d26f4a5dd 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -82,64 +82,30 @@ class StorageStatusElement(Element): return u"%d" % size @renderer - def disk_total(self, req, tag): - val = self._get_storage_stat("storage_server.disk_total") - return tag(self.render_space(val)) + def storage_stats(self, req, tag): + # Render storage status table that appears near the top of the page. + total = self._get_storage_stat("storage_server.disk_total") + used = self._get_storage_stat("storage_server.disk_used") + free_root = self._get_storage_stat("storage_server.disk_free_for_root") + free_nonroot = self._get_storage_stat("storage_server.disk_free_for_nonroot") + reserved = self._get_storage_stat("storage_server.reserved_space") + available = self._get_storage_stat("storage_server.disk_avail") - @renderer - def disk_total_abbrev(self, req, tag): - val = self._get_storage_stat("storage_server.disk_total") - return tag(self.render_abbrev_space(val)) - - @renderer - def disk_used(self, req, tag): - val = self._get_storage_stat("storage_server.disk_used") - return tag(self.render_space(val)) - - @renderer - def disk_used_abbrev(self, req, tag): - val = self._get_storage_stat("storage_server.disk_used") - return tag(self.render_abbrev_space(val)) - - @renderer - def disk_free_for_root(self, req, tag): - val = self._get_storage_stat("storage_server.disk_free_for_root") - return tag(self.render_space(val)) - - @renderer - def disk_free_for_root_abbrev(self, req, tag): - val = self._get_storage_stat("storage_server.disk_free_for_root") - return tag(self.render_abbrev_space(val)) - - @renderer - def disk_free_for_nonroot(self, req, tag): - val = self._get_storage_stat("storage_server.disk_free_for_nonroot") - return tag(self.render_space(val)) - - @renderer - def disk_free_for_nonroot_abbrev(self, req, tag): - val = self._get_storage_stat("storage_server.disk_free_for_nonroot") - return tag(self.render_abbrev_space(val)) - - @renderer - def reserved_space(self, req, tag): - val = self._get_storage_stat("storage_server.reserved_space") - return tag(self.render_space(val)) - - @renderer - def reserved_space_abbrev(self, req, tag): - val = self._get_storage_stat("storage_server.reserved_space") - return tag(self.render_abbrev_space(val)) - - @renderer - def disk_avail(self, req, tag): - val = self._get_storage_stat("storage_server.disk_avail") - return tag(self.render_space(val)) - - @renderer - def disk_avail_abbrev(self, req, tag): - val = self._get_storage_stat("storage_server.disk_avail") - return tag(self.render_abbrev_space(val)) + tag.fillSlots( + disk_total = self.render_space(total), + disk_total_abbrev = self.render_abbrev_space(total), + disk_used = self.render_space(used), + disk_used_abbrev = self.render_abbrev_space(used), + disk_free_for_root = self.render_space(free_root), + disk_free_for_root_abbrev = self.render_abbrev_space(free_root), + disk_free_for_nonroot = self.render_space(free_nonroot), + disk_free_for_nonroot_abbrev = self.render_abbrev_space(free_nonroot), + reserved_space = self.render_space(reserved), + reserved_space_abbrev = self.render_abbrev_space(reserved), + disk_avail = self.render_space(available), + disk_avail_abbrev = self.render_abbrev_space(available) + ) + return tag @renderer def accepting_immutable_shares(self, req, tag): diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml index d052fbabd..bbf3d2c8a 100644 --- a/src/allmydata/web/storage_status.xhtml +++ b/src/allmydata/web/storage_status.xhtml @@ -11,15 +11,15 @@

      Storage Server Status

      - +
      - - + + - - + + - - + + - - + + - - + + - - + +
      Total disk space:()()
      Disk space used:- ()- ()
      @@ -28,18 +28,18 @@
      Disk space free (root):()() [see 1]
      Disk space free (non-root):()() [see 2]
      Reserved space:- ()- ()
      @@ -48,8 +48,8 @@
      Space Available to Tahoe:()()
      From 554c477cea5a3c777794923d406137aa1be46f18 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 25 Feb 2020 08:52:45 -0500 Subject: [PATCH 0029/1054] Prefix member variable with "_" --- src/allmydata/test/test_storage.py | 2 +- src/allmydata/web/storage.py | 40 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 0a176bd60..56d281688 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -2968,7 +2968,7 @@ def renderSynchronously(ss): """ :param _StorageStatus ss: a StorageStatus instance. """ - elem = StorageStatusElement(ss.storage, ss.nickname) + elem = StorageStatusElement(ss._storage, ss._nickname) deferred = flattenString(None, elem) return unittest.TestCase().successResultOf(deferred) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index d26f4a5dd..9b3daa0cb 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -33,16 +33,16 @@ class StorageStatusElement(Element): :param string nickname: friendly name for storage. """ super(StorageStatusElement, self).__init__() - self.storage = storage - self.nick = nickname + self._storage = storage + self._nickname = nickname @renderer def nickname(self, req, tag): - return self.nick + return self._nickname @renderer def nodeid(self, req, tag): - return idlib.nodeid_b2a(self.storage.my_nodeid) + return idlib.nodeid_b2a(self._storage.my_nodeid) def _get_storage_stat(self, key): """Get storage server statistics. @@ -69,7 +69,7 @@ class StorageStatusElement(Element): :param str key: storage server statistic we want to know. """ - return self.storage.get_stats().get(key) + return self._storage.get_stats().get(key) def render_abbrev_space(self, size): if size is None: @@ -114,7 +114,7 @@ class StorageStatusElement(Element): @renderer def last_complete_bucket_count(self, req, tag): - s = self.storage.bucket_counter.get_state() + s = self._storage.bucket_counter.get_state() count = s.get("last-complete-bucket-count") if count is None: return "Not computed yet" @@ -122,7 +122,7 @@ class StorageStatusElement(Element): @renderer def count_crawler_status(self, req, tag): - p = self.storage.bucket_counter.get_progress() + p = self._storage.bucket_counter.get_progress() return self.format_crawler_progress(p) def format_crawler_progress(self, p): @@ -152,13 +152,13 @@ class StorageStatusElement(Element): @renderer def storage_running(self, req, tag): - if self.storage: + if self._storage: return tag return T.h1("No Storage Server Running") @renderer def lease_expiration_enabled(self, req, tag): - lc = self.storage.lease_checker + lc = self._storage.lease_checker if lc.expiration_enabled: return tag("Enabled: expired leases will be removed") else: @@ -166,7 +166,7 @@ class StorageStatusElement(Element): @renderer def lease_expiration_mode(self, req, tag): - lc = self.storage.lease_checker + lc = self._storage.lease_checker if lc.mode == "age": if lc.override_lease_duration is None: tag("Leases will expire naturally, probably 31 days after " @@ -189,13 +189,13 @@ class StorageStatusElement(Element): @renderer def lease_current_cycle_progress(self, req, tag): - lc = self.storage.lease_checker + lc = self._storage.lease_checker p = lc.get_progress() return tag(self.format_crawler_progress(p)) @renderer def lease_current_cycle_results(self, req, tag): - lc = self.storage.lease_checker + lc = self._storage.lease_checker p = lc.get_progress() if not p["cycle-in-progress"]: return "" @@ -252,7 +252,7 @@ class StorageStatusElement(Element): @renderer def lease_last_cycle_results(self, req, tag): - lc = self.storage.lease_checker + lc = self._storage.lease_checker h = lc.get_state()["history"] if not h: return "" @@ -304,18 +304,18 @@ class StorageStatusElement(Element): class StorageStatus(MultiFormatResource): def __init__(self, storage, nickname=""): super(StorageStatus, self).__init__() - self.storage = storage - self.nickname = nickname + self._storage = storage + self._nickname = nickname def render_HTML(self, req): - return renderElement(req, StorageStatusElement(self.storage, self.nickname)) + return renderElement(req, StorageStatusElement(self._storage, self._nickname)) def render_JSON(self, req): req.setHeader("content-type", "text/plain") - d = {"stats": self.storage.get_stats(), - "bucket-counter": self.storage.bucket_counter.get_state(), - "lease-checker": self.storage.lease_checker.get_state(), - "lease-checker-progress": self.storage.lease_checker.get_progress(), + d = {"stats": self._storage.get_stats(), + "bucket-counter": self._storage.bucket_counter.get_state(), + "lease-checker": self._storage.lease_checker.get_state(), + "lease-checker-progress": self._storage.lease_checker.get_progress(), } return json.dumps(d, indent=1) + "\n" From 110734daf065e7b2706e7d92b2361c21954c11eb Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 5 Mar 2020 15:45:18 -0500 Subject: [PATCH 0030/1054] Use a helper to exercise render() in storage test cases --- src/allmydata/test/test_storage.py | 50 ++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 56d281688..86b23cd48 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -30,6 +30,7 @@ from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \ from allmydata.interfaces import BadWriteEnablerError from allmydata.test.common import LoggingServiceParent, ShouldFailMixin from allmydata.test.common_web import WebRenderingMixin +from nevow.testutil import FakeRequest from allmydata.test.no_network import NoNetworkServer from allmydata.web.storage import ( StorageStatus, @@ -2972,6 +2973,29 @@ def renderSynchronously(ss): deferred = flattenString(None, elem) return unittest.TestCase().successResultOf(deferred) +def renderDeferred(resource, **kwargs): + """ + Use this to exercise an overridden MultiFormatResource.render(), + usually for output=json or render_GET. It returns a Deferred. + + :param _MultiFormatResource resource: an HTTP resource to be rendered. + + """ + # We should be using twisted.web's DummyRequest here instead of + # nevow's FakeRequest, but right now it is a bit of a problem: see + # web/common.py. MultiFormatResource.render() makes a get_arg() + # call, which does a IRequest(ctx_or_req). IRequest can handle + # FakeRequest, but it can't handle DummyRequest. + req = FakeRequest(**kwargs) + req.fields = None + d = defer.maybeDeferred(resource.render, req) + def _done(res): + if isinstance(res, str): + return res + req.v + return req.v + d.addCallback(_done) + return d + class MyBucketCountingCrawler(BucketCountingCrawler): def finished_prefix(self, cycle, prefix): BucketCountingCrawler.finished_prefix(self, cycle, prefix) @@ -3291,7 +3315,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failIfEqual(sr2["configured-diskbytes"], None) self.failIfEqual(sr2["original-sharebytes"], None) d.addCallback(_after_first_bucket) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html_in_cycle(html): s = remove_tags(html) self.failUnlessIn("So far, this cycle has examined " @@ -3366,7 +3390,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failUnlessEqual(count_leases(mutable_si_2), 1) self.failUnlessEqual(count_leases(mutable_si_3), 2) d.addCallback(_after_first_cycle) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html(html): s = remove_tags(html) self.failUnlessIn("recovered: 0 shares, 0 buckets " @@ -3466,7 +3490,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): d2.addCallback(_after_first_bucket) return d2 d.addCallback(_after_first_bucket) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html_in_cycle(html): s = remove_tags(html) # the first bucket encountered gets deleted, and its prefix @@ -3525,7 +3549,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failUnless(rec["configured-diskbytes"] >= 0, rec["configured-diskbytes"]) d.addCallback(_after_first_cycle) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html(html): s = remove_tags(html) self.failUnlessIn("Expiration Enabled: expired leases will be removed", s) @@ -3610,7 +3634,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): d2.addCallback(_after_first_bucket) return d2 d.addCallback(_after_first_bucket) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html_in_cycle(html): s = remove_tags(html) # the first bucket encountered gets deleted, and its prefix @@ -3671,7 +3695,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failUnless(rec["configured-diskbytes"] >= 0, rec["configured-diskbytes"]) d.addCallback(_after_first_cycle) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html(html): s = remove_tags(html) self.failUnlessIn("Expiration Enabled:" @@ -3733,7 +3757,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failUnlessEqual(count_shares(mutable_si_3), 1) self.failUnlessEqual(count_leases(mutable_si_3), 2) d.addCallback(_after_first_cycle) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html(html): s = remove_tags(html) self.failUnlessIn("The following sharetypes will be expired: immutable.", s) @@ -3790,7 +3814,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): self.failUnlessEqual(count_shares(mutable_si_2), 0) self.failUnlessEqual(count_shares(mutable_si_3), 0) d.addCallback(_after_first_cycle) - d.addCallback(lambda ign: self.render1(webstatus)) + d.addCallback(lambda ign: renderDeferred(webstatus)) def _check_html(html): s = remove_tags(html) self.failUnlessIn("The following sharetypes will be expired: mutable.", s) @@ -4021,7 +4045,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): # it also turns all tuples into lists self.failUnlessEqual(corrupt_shares, [[first_b32, 0]]) d.addCallback(_check_json) - d.addCallback(lambda ign: self.render1(w)) + d.addCallback(lambda ign: renderDeferred(w)) def _check_html(html): s = remove_tags(html) self.failUnlessIn("Corrupt shares: SI %s shnum 0" % first_b32, s) @@ -4046,7 +4070,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): corrupt_shares = last["corrupt-shares"] self.failUnlessEqual(corrupt_shares, [[first_b32, 0]]) d.addCallback(_check_json_history) - d.addCallback(lambda ign: self.render1(w)) + d.addCallback(lambda ign: renderDeferred(w)) def _check_html_history(html): s = remove_tags(html) self.failUnlessIn("Corrupt shares: SI %s shnum 0" % first_b32, s) @@ -4060,7 +4084,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): return d def render_json(self, page): - d = self.render1(page, args={"t": ["json"]}) + d = renderDeferred(page, args={"t": ["json"]}) return d class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): @@ -4083,7 +4107,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): ss = StorageServer(basedir, nodeid) ss.setServiceParent(self.s) w = StorageStatus(ss, "nickname") - d = self.render1(w) + d = renderDeferred(w) def _check_html(html): self.failUnlessIn("

      Storage Server Status

      ", html) s = remove_tags(html) @@ -4104,7 +4128,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): return d def render_json(self, page): - d = self.render1(page, args={"t": ["json"]}) + d = renderDeferred(page, args={"t": ["json"]}) return d def test_status_no_disk_stats(self): From f1fe3a75884c2073310727a5f4c4127bfb928645 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 5 Mar 2020 15:46:09 -0500 Subject: [PATCH 0031/1054] Get rid of WebRenderingMixin in storage test --- src/allmydata/test/test_storage.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 86b23cd48..cff8c3e22 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -29,7 +29,6 @@ from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \ SHARE_HASH_CHAIN_SIZE from allmydata.interfaces import BadWriteEnablerError from allmydata.test.common import LoggingServiceParent, ShouldFailMixin -from allmydata.test.common_web import WebRenderingMixin from nevow.testutil import FakeRequest from allmydata.test.no_network import NoNetworkServer from allmydata.web.storage import ( @@ -3185,7 +3184,7 @@ class InstrumentedStorageServer(StorageServer): class No_ST_BLOCKS_StorageServer(StorageServer): LeaseCheckerClass = No_ST_BLOCKS_LeaseCheckingCrawler -class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): +class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): def setUp(self): self.s = service.MultiService() @@ -4087,7 +4086,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): d = renderDeferred(page, args={"t": ["json"]}) return d -class WebStatus(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin): +class WebStatus(unittest.TestCase, pollmixin.PollMixin): def setUp(self): self.s = service.MultiService() From bae32179bf467e8802d235e5bcd4c1c18411a5c9 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 5 Mar 2020 15:57:12 -0500 Subject: [PATCH 0032/1054] Remove StorageStatus.renderHTTP This was added to please the test suite. Pleased to remove it! --- src/allmydata/web/storage.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 9b3daa0cb..63ac47282 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -318,19 +318,3 @@ class StorageStatus(MultiFormatResource): "lease-checker-progress": self._storage.lease_checker.get_progress(), } return json.dumps(d, indent=1) + "\n" - - # to appease the test suite - def renderHTTP(self, ctx=None): - """Send HTML or JSON formatted data, based on request. - - This function contains a bit of nevow-ism, but since this is - only called from the test suite, the nevow-ism should go away - as we update things. - - :param _nevow.context.WovenContext ctx: context is passed on - from the test suite. We get a request out of this - context, and use the request to render a result. - - """ - from nevow.inevow import IRequest - return self.render(IRequest(ctx)) From 9c7357bc61dd74e4b15ae0da2d28a5275acd6b5e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 30 Mar 2020 16:15:44 -0400 Subject: [PATCH 0033/1054] Remove an extraneous directive `t:data` is not really a Twisted template directive. Added my mistake, removing now. --- src/allmydata/web/storage_status.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/storage_status.xhtml b/src/allmydata/web/storage_status.xhtml index bbf3d2c8a..354f9f177 100644 --- a/src/allmydata/web/storage_status.xhtml +++ b/src/allmydata/web/storage_status.xhtml @@ -57,7 +57,7 @@
      • Server Nickname:
      • Server Nodeid:
      • -
      • Accepting new shares: +
      • Accepting new shares:
      • Total buckets: From 8c92187d9263fa8d092c62c1305200191e850ec5 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 6 Apr 2020 19:00:55 -0400 Subject: [PATCH 0034/1054] Avoid using nevow FakeRequest in storage test. Use twisted.web.server.Request instead, with a DummyChannel. There's still one line of inevitable nevow now, because of code in web/common.py; but that should be easily replaceable once we switch that over. --- src/allmydata/test/test_storage.py | 47 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index cff8c3e22..40b9b52e9 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -5,6 +5,16 @@ from twisted.trial import unittest from twisted.internet import defer from twisted.application import service from twisted.web.template import flattenString + +# We need to use `nevow.inevow.IRequest` for now for compatibility +# with the code in web/common.py. Once nevow bits are gone from +# web/common.py, we can use `twisted.web.iweb.IRequest` here. +from nevow.inevow import IRequest + +from twisted.web.server import Request +from twisted.web.test.test_web import DummyChannel +from zope.interface import implements + from foolscap.api import fireEventually import itertools from allmydata import interfaces @@ -29,7 +39,6 @@ from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \ SHARE_HASH_CHAIN_SIZE from allmydata.interfaces import BadWriteEnablerError from allmydata.test.common import LoggingServiceParent, ShouldFailMixin -from nevow.testutil import FakeRequest from allmydata.test.no_network import NoNetworkServer from allmydata.web.storage import ( StorageStatus, @@ -2972,28 +2981,20 @@ def renderSynchronously(ss): deferred = flattenString(None, elem) return unittest.TestCase().successResultOf(deferred) -def renderDeferred(resource, **kwargs): - """ - Use this to exercise an overridden MultiFormatResource.render(), - usually for output=json or render_GET. It returns a Deferred. +def renderDeferred(ss): + elem = StorageStatusElement(ss._storage, ss._nickname) + return flattenString(None, elem) - :param _MultiFormatResource resource: an HTTP resource to be rendered. +class JSONRequest(Request): + implements(IRequest) - """ - # We should be using twisted.web's DummyRequest here instead of - # nevow's FakeRequest, but right now it is a bit of a problem: see - # web/common.py. MultiFormatResource.render() makes a get_arg() - # call, which does a IRequest(ctx_or_req). IRequest can handle - # FakeRequest, but it can't handle DummyRequest. - req = FakeRequest(**kwargs) - req.fields = None - d = defer.maybeDeferred(resource.render, req) - def _done(res): - if isinstance(res, str): - return res + req.v - return req.v - d.addCallback(_done) - return d + def __init__(self, **kwargs): + Request.__init__(self, DummyChannel(), **kwargs) + self.args = {"t": ["json"]} + self.fields = {} + +def renderJSON(resource): + return resource.render(JSONRequest()) class MyBucketCountingCrawler(BucketCountingCrawler): def finished_prefix(self, cycle, prefix): @@ -4083,7 +4084,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): return d def render_json(self, page): - d = renderDeferred(page, args={"t": ["json"]}) + d = renderJSON(page) return d class WebStatus(unittest.TestCase, pollmixin.PollMixin): @@ -4127,7 +4128,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin): return d def render_json(self, page): - d = renderDeferred(page, args={"t": ["json"]}) + d = renderJSON(page) return d def test_status_no_disk_stats(self): From 3e7dea7dda3bee3dfb2713297c10f3fac1b2698f Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 6 Apr 2020 19:01:22 -0400 Subject: [PATCH 0035/1054] Wrap renderer results in tags --- src/allmydata/web/storage.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py index 63ac47282..ba6609456 100644 --- a/src/allmydata/web/storage.py +++ b/src/allmydata/web/storage.py @@ -38,11 +38,11 @@ class StorageStatusElement(Element): @renderer def nickname(self, req, tag): - return self._nickname + return tag(self._nickname) @renderer def nodeid(self, req, tag): - return idlib.nodeid_b2a(self._storage.my_nodeid) + return tag(idlib.nodeid_b2a(self._storage.my_nodeid)) def _get_storage_stat(self, key): """Get storage server statistics. @@ -110,20 +110,20 @@ class StorageStatusElement(Element): @renderer def accepting_immutable_shares(self, req, tag): accepting = self._get_storage_stat("storage_server.accepting_immutable_shares") - return {True: "Yes", False: "No"}[bool(accepting)] + return tag({True: "Yes", False: "No"}[bool(accepting)]) @renderer def last_complete_bucket_count(self, req, tag): s = self._storage.bucket_counter.get_state() count = s.get("last-complete-bucket-count") if count is None: - return "Not computed yet" - return str(count) + return tag("Not computed yet") + return tag(str(count)) @renderer def count_crawler_status(self, req, tag): p = self._storage.bucket_counter.get_progress() - return self.format_crawler_progress(p) + return tag(self.format_crawler_progress(p)) def format_crawler_progress(self, p): cycletime = p["estimated-time-per-cycle"] From 8b7ef33b3d3b7caf6a91f6f803111288d2bb5ed4 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 6 Apr 2020 19:05:17 -0400 Subject: [PATCH 0036/1054] Remove redundant render_json() method --- src/allmydata/test/test_storage.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 40b9b52e9..e20a89da6 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -3399,7 +3399,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): "(2 mutable / 2 immutable),", s) self.failUnlessIn("but expiration was not enabled", s) d.addCallback(_check_html) - d.addCallback(lambda ign: self.render_json(webstatus)) + d.addCallback(lambda ign: renderJSON(webstatus)) def _check_json(raw): data = json.loads(raw) self.failUnlessIn("lease-checker", data) @@ -4036,7 +4036,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): self.failUnlessEqual(so_far["corrupt-shares"], [(first_b32, 0)]) d.addCallback(_after_first_bucket) - d.addCallback(lambda ign: self.render_json(w)) + d.addCallback(lambda ign: renderJSON(w)) def _check_json(raw): data = json.loads(raw) # grr. json turns all dict keys into strings. @@ -4063,7 +4063,7 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): self.failUnlessEqual(rec["examined-shares"], 3) self.failUnlessEqual(last["corrupt-shares"], [(first_b32, 0)]) d.addCallback(_after_first_cycle) - d.addCallback(lambda ign: self.render_json(w)) + d.addCallback(lambda ign: renderJSON(w)) def _check_json_history(raw): data = json.loads(raw) last = data["lease-checker"]["history"]["0"] @@ -4083,9 +4083,6 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): d.addBoth(_cleanup) return d - def render_json(self, page): - d = renderJSON(page) - return d class WebStatus(unittest.TestCase, pollmixin.PollMixin): @@ -4116,7 +4113,7 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin): self.failUnlessIn("Accepting new shares: Yes", s) self.failUnlessIn("Reserved space: - 0 B (0)", s) d.addCallback(_check_html) - d.addCallback(lambda ign: self.render_json(w)) + d.addCallback(lambda ign: renderJSON(w)) def _check_json(raw): data = json.loads(raw) s = data["stats"] @@ -4127,9 +4124,6 @@ class WebStatus(unittest.TestCase, pollmixin.PollMixin): d.addCallback(_check_json) return d - def render_json(self, page): - d = renderJSON(page) - return d def test_status_no_disk_stats(self): def call_get_disk_stats(whichdir, reserved_space=0): From 72b8f720802f91d5aef25643b326d1ba98b54cc2 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 6 Apr 2020 19:13:08 -0400 Subject: [PATCH 0037/1054] Add docstrings to storage test helpers --- src/allmydata/test/test_storage.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index e20a89da6..b69713df5 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -2975,6 +2975,8 @@ def remove_tags(s): def renderSynchronously(ss): """ + Return fully rendered HTML document. + :param _StorageStatus ss: a StorageStatus instance. """ elem = StorageStatusElement(ss._storage, ss._nickname) @@ -2982,10 +2984,20 @@ def renderSynchronously(ss): return unittest.TestCase().successResultOf(deferred) def renderDeferred(ss): + """ + Return a `Deferred` HTML renderer. + + :param _StorageStatus ss: a StorageStatus instance. + """ elem = StorageStatusElement(ss._storage, ss._nickname) return flattenString(None, elem) class JSONRequest(Request): + """ + A Request with t=json argument added to it. + + This is useful to invoke a Resouce.render_JSON() method. + """ implements(IRequest) def __init__(self, **kwargs): @@ -2994,6 +3006,11 @@ class JSONRequest(Request): self.fields = {} def renderJSON(resource): + """Exercise resouce.render_JSON() + + :param _MultiFormatResource resouce: A `twisted.web.resouce.Resource` + that contains a render_JSON() method. + """ return resource.render(JSONRequest()) class MyBucketCountingCrawler(BucketCountingCrawler): From b2b706198042fa39ddc4959d3804647340c8bb24 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 6 Apr 2020 19:18:15 -0400 Subject: [PATCH 0038/1054] Refactor storage test helpers Rewrite `renderSynchronously()` to use `renderDeferred()` --- src/allmydata/test/test_storage.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index b69713df5..f50c3b352 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -2979,9 +2979,7 @@ def renderSynchronously(ss): :param _StorageStatus ss: a StorageStatus instance. """ - elem = StorageStatusElement(ss._storage, ss._nickname) - deferred = flattenString(None, elem) - return unittest.TestCase().successResultOf(deferred) + return unittest.TestCase().successResultOf(renderDeferred(ss)) def renderDeferred(ss): """ From 82cd5a87fe9582dd1de8e021b8b8a8459d3ba499 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 6 Apr 2020 21:48:49 -0400 Subject: [PATCH 0039/1054] Use DummyRequest in storage tests Using twisted.web.server.Request causes test_new_style_classes to fail like so: Traceback (most recent call last): Failure: testtools.testresult.real._StringException: Traceback (most recent call last): File ".tox/coverage/lib/python2.7/site-packages/allmydata/test/test_python2_regressions.py", line 69, in test_new_style_classes "Expected to find no classic classes.", File ".tox/coverage/lib/python2.7/site-packages/testtools/testcase.py", line 502, in assertThat raise mismatch_error testtools.matchers._impl.MismatchError: !=: reference = set([]) actual = set([]) : Expected to find no classic classes. Seems that `DummyRequest` is an acceptable new style class. --- src/allmydata/test/test_storage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index f50c3b352..a0816a6ce 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -11,8 +11,8 @@ from twisted.web.template import flattenString # web/common.py, we can use `twisted.web.iweb.IRequest` here. from nevow.inevow import IRequest -from twisted.web.server import Request -from twisted.web.test.test_web import DummyChannel +# from twisted.web.server import Request +from twisted.web.test.test_web import DummyRequest from zope.interface import implements from foolscap.api import fireEventually @@ -2990,7 +2990,7 @@ def renderDeferred(ss): elem = StorageStatusElement(ss._storage, ss._nickname) return flattenString(None, elem) -class JSONRequest(Request): +class JSONRequest(DummyRequest): """ A Request with t=json argument added to it. @@ -2999,7 +2999,7 @@ class JSONRequest(Request): implements(IRequest) def __init__(self, **kwargs): - Request.__init__(self, DummyChannel(), **kwargs) + DummyRequest.__init__(self, b"/", **kwargs) self.args = {"t": ["json"]} self.fields = {} From 201c08dbe5d0e7c585329261e24082670d999f88 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 20 Apr 2020 14:35:32 -0400 Subject: [PATCH 0040/1054] Declare JSON request interface using @implementer "zope.interface.implements(IRequest)" is deprectated in favor of "@zope.interface.implementer(IRequest)" decorator. --- src/allmydata/test/test_storage.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index a0816a6ce..23920a27a 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -11,9 +11,8 @@ from twisted.web.template import flattenString # web/common.py, we can use `twisted.web.iweb.IRequest` here. from nevow.inevow import IRequest -# from twisted.web.server import Request -from twisted.web.test.test_web import DummyRequest -from zope.interface import implements +from twisted.web.test.requesthelper import DummyRequest +from zope.interface import implementer from foolscap.api import fireEventually import itertools @@ -2990,14 +2989,13 @@ def renderDeferred(ss): elem = StorageStatusElement(ss._storage, ss._nickname) return flattenString(None, elem) +@implementer(IRequest) class JSONRequest(DummyRequest): """ A Request with t=json argument added to it. This is useful to invoke a Resouce.render_JSON() method. """ - implements(IRequest) - def __init__(self, **kwargs): DummyRequest.__init__(self, b"/", **kwargs) self.args = {"t": ["json"]} From b3feaae644de727bf564a50df83e1ff4b733f76a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 21 Apr 2020 10:29:18 -0400 Subject: [PATCH 0041/1054] Use an inner JSONRequest class with renderJSON Once nevow is removed from web/common.py, we can simplify renderJSON(), like so: def renderJSON(): req = Request() req.args = {"t": ["json"]} req.fields = {} return resource.render(req) But for now we have to live with an inner class that implements the nevow.inevow.IRequest interface. --- src/allmydata/test/test_storage.py | 31 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 23920a27a..6c5f6e937 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -11,7 +11,8 @@ from twisted.web.template import flattenString # web/common.py, we can use `twisted.web.iweb.IRequest` here. from nevow.inevow import IRequest -from twisted.web.test.requesthelper import DummyRequest +from twisted.web.server import Request +from twisted.web.test.requesthelper import DummyChannel from zope.interface import implementer from foolscap.api import fireEventually @@ -2989,24 +2990,20 @@ def renderDeferred(ss): elem = StorageStatusElement(ss._storage, ss._nickname) return flattenString(None, elem) -@implementer(IRequest) -class JSONRequest(DummyRequest): - """ - A Request with t=json argument added to it. - - This is useful to invoke a Resouce.render_JSON() method. - """ - def __init__(self, **kwargs): - DummyRequest.__init__(self, b"/", **kwargs) - self.args = {"t": ["json"]} - self.fields = {} - def renderJSON(resource): - """Exercise resouce.render_JSON() + """Render a JSON from the given resource.""" + + @implementer(IRequest) + class JSONRequest(Request): + """ + A Request with t=json argument added to it. This is useful to + invoke a Resouce.render_JSON() method. + """ + def __init__(self): + Request.__init__(self, DummyChannel()) + self.args = {"t": ["json"]} + self.fields = {} - :param _MultiFormatResource resouce: A `twisted.web.resouce.Resource` - that contains a render_JSON() method. - """ return resource.render(JSONRequest()) class MyBucketCountingCrawler(BucketCountingCrawler): From 8d8281cd3eda32ae6fa158170ca6039a56e608fd Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 24 Apr 2020 14:23:15 -0400 Subject: [PATCH 0042/1054] Add autobahn derivation for nixos Copied from nixos-19.09-small/pkgs/development/python-modules/autobahn/default.nix --- nix/autobahn.nix | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 nix/autobahn.nix diff --git a/nix/autobahn.nix b/nix/autobahn.nix new file mode 100644 index 000000000..7d1e365b2 --- /dev/null +++ b/nix/autobahn.nix @@ -0,0 +1,34 @@ +{ lib, buildPythonPackage, fetchPypi, isPy3k, + six, txaio, twisted, zope_interface, cffi, trollius, futures, + mock, pytest, cryptography, pynacl +}: +buildPythonPackage rec { + pname = "autobahn"; + version = "19.8.1"; + + src = fetchPypi { + inherit pname version; + sha256 = "294e7381dd54e73834354832604ae85567caf391c39363fed0ea2bfa86aa4304"; + }; + + propagatedBuildInputs = [ six txaio twisted zope_interface cffi cryptography pynacl ] ++ + (lib.optionals (!isPy3k) [ trollius futures ]); + + checkInputs = [ mock pytest ]; + checkPhase = '' + runHook preCheck + USE_TWISTED=true py.test $out + runHook postCheck + ''; + + # Tests do no seem to be compatible yet with pytest 5.1 + # https://github.com/crossbario/autobahn-python/issues/1235 + doCheck = false; + + meta = with lib; { + description = "WebSocket and WAMP in Python for Twisted and asyncio."; + homepage = "https://crossbar.io/autobahn"; + license = licenses.mit; + maintainers = with maintainers; [ nand0p ]; + }; +} From e7c1c78581b79309c726ebfe082f4aade62feb69 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 24 Apr 2020 14:24:01 -0400 Subject: [PATCH 0043/1054] Remove trollius from nixos autobahn derivation --- nix/autobahn.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/autobahn.nix b/nix/autobahn.nix index 7d1e365b2..83148c4f8 100644 --- a/nix/autobahn.nix +++ b/nix/autobahn.nix @@ -1,5 +1,5 @@ { lib, buildPythonPackage, fetchPypi, isPy3k, - six, txaio, twisted, zope_interface, cffi, trollius, futures, + six, txaio, twisted, zope_interface, cffi, futures, mock, pytest, cryptography, pynacl }: buildPythonPackage rec { @@ -12,7 +12,7 @@ buildPythonPackage rec { }; propagatedBuildInputs = [ six txaio twisted zope_interface cffi cryptography pynacl ] ++ - (lib.optionals (!isPy3k) [ trollius futures ]); + (lib.optionals (!isPy3k) [ futures ]); checkInputs = [ mock pytest ]; checkPhase = '' From 756dd2524702c20cc97cf783dd5027e38349e1ec Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 24 Apr 2020 14:29:59 -0400 Subject: [PATCH 0044/1054] Use our own autobahn nixos derivation --- nix/overlays.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nix/overlays.nix b/nix/overlays.nix index 08d11306e..2472c1a37 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -7,6 +7,10 @@ self: super: { # conflicts with the packaged version of Twisted. Supply our own # slightly newer version. nevow = python-super.callPackage ./nevow.nix { }; + # NixOS autobahn package has trollius as a dependency, although + # it is optional. Trollius is no longer maintained and fails on + # CI. + autobahn = python-super.callPackage ./autobahn.nix { }; }; }; } From d27e5e0afe1e8224f1572dd5df50b9a3738bfddb Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 24 Apr 2020 19:01:49 -0400 Subject: [PATCH 0045/1054] Add newsfragment --- newsfragments/3304.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3304.minor diff --git a/newsfragments/3304.minor b/newsfragments/3304.minor new file mode 100644 index 000000000..e69de29bb From 5fd855a600123ce8ee6eb3aa6757465399d146b3 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 24 Apr 2020 21:16:57 -0400 Subject: [PATCH 0046/1054] Update comment about trollius --- nix/overlays.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nix/overlays.nix b/nix/overlays.nix index 2472c1a37..2a9fe8e5e 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -8,8 +8,7 @@ self: super: { # slightly newer version. nevow = python-super.callPackage ./nevow.nix { }; # NixOS autobahn package has trollius as a dependency, although - # it is optional. Trollius is no longer maintained and fails on - # CI. + # it is optional. Trollius is unmaintained and fails on CI. autobahn = python-super.callPackage ./autobahn.nix { }; }; }; From 4b0c730adf8c7f9240c50cef156627a1a6269c80 Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 7 Dec 2019 03:18:46 -0700 Subject: [PATCH 0047/1054] use 'with open' correctly --- src/allmydata/scripts/cli.py | 3 +- src/allmydata/storage/immutable.py | 68 ++++++++++++++---------------- src/allmydata/test/no_network.py | 31 ++++++++------ 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py index 31ef26510..379e1d212 100644 --- a/src/allmydata/scripts/cli.py +++ b/src/allmydata/scripts/cli.py @@ -38,7 +38,8 @@ class FileStoreOptions(BaseOptions): raise usage.UsageError(msg) else: node_url_file = os.path.join(self['node-directory'], "node.url") - self['node-url'] = open(node_url_file, "r").read().strip() + with open(node_url_file, "r") as f: + self['node-url'] = f.read().strip() if self['node-url'][-1] != "/": self['node-url'] += "/" diff --git a/src/allmydata/storage/immutable.py b/src/allmydata/storage/immutable.py index f66eec594..7f69fb3b8 100644 --- a/src/allmydata/storage/immutable.py +++ b/src/allmydata/storage/immutable.py @@ -50,7 +50,6 @@ class ShareFile(object): # it. Also construct the metadata. assert not os.path.exists(self.home) fileutil.make_dirs(os.path.dirname(self.home)) - f = open(self.home, 'wb') # The second field -- the four-byte share data length -- is no # longer used as of Tahoe v1.3.0, but we continue to write it in # there in case someone downgrades a storage server from >= @@ -60,15 +59,14 @@ class ShareFile(object): # the largest length that can fit into the field. That way, even # if this does happen, the old < v1.3.0 server will still allow # clients to read the first part of the share. - f.write(struct.pack(">LLL", 1, min(2**32-1, max_size), 0)) - f.close() + with open(self.home, 'wb') as f: + f.write(struct.pack(">LLL", 1, min(2**32-1, max_size), 0)) self._lease_offset = max_size + 0x0c self._num_leases = 0 else: - f = open(self.home, 'rb') - filesize = os.path.getsize(self.home) - (version, unused, num_leases) = struct.unpack(">LLL", f.read(0xc)) - f.close() + with open(self.home, 'rb') as f: + filesize = os.path.getsize(self.home) + (version, unused, num_leases) = struct.unpack(">LLL", f.read(0xc)) if version != 1: msg = "sharefile %s had version %d but we wanted 1" % \ (filename, version) @@ -88,21 +86,20 @@ class ShareFile(object): actuallength = max(0, min(length, self._lease_offset-seekpos)) if actuallength == 0: return "" - f = open(self.home, 'rb') - f.seek(seekpos) - return f.read(actuallength) + with open(self.home, 'rb') as f: + f.seek(seekpos) + return f.read(actuallength) def write_share_data(self, offset, data): length = len(data) precondition(offset >= 0, offset) if self._max_size is not None and offset+length > self._max_size: raise DataTooLargeError(self._max_size, offset, length) - f = open(self.home, 'rb+') - real_offset = self._data_offset+offset - f.seek(real_offset) - assert f.tell() == real_offset - f.write(data) - f.close() + with open(self.home, 'rb+') as f: + real_offset = self._data_offset+offset + f.seek(real_offset) + assert f.tell() == real_offset + f.write(data) def _write_lease_record(self, f, lease_number, lease_info): offset = self._lease_offset + lease_number * self.LEASE_SIZE @@ -124,20 +121,19 @@ class ShareFile(object): def get_leases(self): """Yields a LeaseInfo instance for all leases.""" - f = open(self.home, 'rb') - (version, unused, num_leases) = struct.unpack(">LLL", f.read(0xc)) - f.seek(self._lease_offset) - for i in range(num_leases): - data = f.read(self.LEASE_SIZE) - if data: - yield LeaseInfo().from_immutable_data(data) + with open(self.home, 'rb') as f: + (version, unused, num_leases) = struct.unpack(">LLL", f.read(0xc)) + f.seek(self._lease_offset) + for i in range(num_leases): + data = f.read(self.LEASE_SIZE) + if data: + yield LeaseInfo().from_immutable_data(data) def add_lease(self, lease_info): - f = open(self.home, 'rb+') - num_leases = self._read_num_leases(f) - self._write_lease_record(f, num_leases, lease_info) - self._write_num_leases(f, num_leases+1) - f.close() + with open(self.home, 'rb+') as f: + num_leases = self._read_num_leases(f) + self._write_lease_record(f, num_leases, lease_info) + self._write_num_leases(f, num_leases+1) def renew_lease(self, renew_secret, new_expire_time): for i,lease in enumerate(self.get_leases()): @@ -146,9 +142,8 @@ class ShareFile(object): if new_expire_time > lease.expiration_time: # yes lease.expiration_time = new_expire_time - f = open(self.home, 'rb+') - self._write_lease_record(f, i, lease) - f.close() + with open(self.home, 'rb+') as f: + self._write_lease_record(f, i, lease) return raise IndexError("unable to renew non-existent lease") @@ -181,12 +176,11 @@ class ShareFile(object): # the same order as they were added, so that if we crash while # doing this, we won't lose any non-cancelled leases. leases = [l for l in leases if l] # remove the cancelled leases - f = open(self.home, 'rb+') - for i,lease in enumerate(leases): - self._write_lease_record(f, i, lease) - self._write_num_leases(f, len(leases)) - self._truncate_leases(f, len(leases)) - f.close() + with open(self.home, 'rb+') as f: + for i, lease in enumerate(leases): + self._write_lease_record(f, i, lease) + self._write_num_leases(f, len(leases)) + self._truncate_leases(f, len(leases)) space_freed = self.LEASE_SIZE * num_leases_removed if not len(leases): space_freed += os.stat(self.home)[stat.ST_SIZE] diff --git a/src/allmydata/test/no_network.py b/src/allmydata/test/no_network.py index 7dfddd54b..0ccead0b9 100644 --- a/src/allmydata/test/no_network.py +++ b/src/allmydata/test/no_network.py @@ -332,13 +332,12 @@ class NoNetworkGrid(service.MultiService): if write_config: from twisted.internet import reactor _, port_endpoint = self.port_assigner.assign(reactor) - f = open(tahoe_cfg_path, "w") - f.write("[node]\n") - f.write("nickname = client-%d\n" % i) - f.write("web.port = {}\n".format(port_endpoint)) - f.write("[storage]\n") - f.write("enabled = false\n") - f.close() + with open(tahoe_cfg_path, "w") as f: + f.write("[node]\n") + f.write("nickname = client-%d\n" % i) + f.write("web.port = {}\n".format(port_endpoint)) + f.write("[storage]\n") + f.write("enabled = false\n") else: _assert(os.path.exists(tahoe_cfg_path), tahoe_cfg_path=tahoe_cfg_path) @@ -523,12 +522,14 @@ class GridTestMixin(object): def copy_shares(self, uri): shares = {} for (shnum, serverid, sharefile) in self.find_uri_shares(uri): - shares[sharefile] = open(sharefile, "rb").read() + with open(sharefile, "rb") as f: + shares[sharefile] = f.read() return shares def restore_all_shares(self, shares): for sharefile, data in shares.items(): - open(sharefile, "wb").write(data) + with open(sharefile, "wb") as f: + f.write(data) def delete_share(self, sharenum_and_serverid_and_sharefile): (shnum, serverid, sharefile) = sharenum_and_serverid_and_sharefile @@ -547,16 +548,20 @@ class GridTestMixin(object): def corrupt_share(self, sharenum_and_serverid_and_sharefile, corruptor_function): (shnum, serverid, sharefile) = sharenum_and_serverid_and_sharefile - sharedata = open(sharefile, "rb").read() + with open(sharefile, "rb") as f: + sharedata = f.read() corruptdata = corruptor_function(sharedata) - open(sharefile, "wb").write(corruptdata) + with open(sharefile, "wb") as f: + f.write(corruptdata) def corrupt_shares_numbered(self, uri, shnums, corruptor, debug=False): for (i_shnum, i_serverid, i_sharefile) in self.find_uri_shares(uri): if i_shnum in shnums: - sharedata = open(i_sharefile, "rb").read() + with open(i_sharefile, "rb") as f: + sharedata = f.read() corruptdata = corruptor(sharedata, debug=debug) - open(i_sharefile, "wb").write(corruptdata) + with open(i_sharefile, "wb") as f: + f.write(corruptdata) def corrupt_all_shares(self, uri, corruptor, debug=False): for (i_shnum, i_serverid, i_sharefile) in self.find_uri_shares(uri): From ca5e1ebe5208d0ec7e649a2eeb49f50d11fcffc9 Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 7 Dec 2019 03:19:06 -0700 Subject: [PATCH 0048/1054] look at correct mock-call object --- src/allmydata/test/test_connections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/test_connections.py b/src/allmydata/test/test_connections.py index ccec13336..3e2806dd0 100644 --- a/src/allmydata/test/test_connections.py +++ b/src/allmydata/test/test_connections.py @@ -114,7 +114,7 @@ class Tor(unittest.TestCase): ) tor_provider = create_tor_provider(reactor, config) h = tor_provider.get_tor_handler() - self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0])) + self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0][1][0])) self.assertIdentical(h, h1) def test_socksport_endpoint(self): @@ -128,7 +128,7 @@ class Tor(unittest.TestCase): ) tor_provider = create_tor_provider(reactor, config) h = tor_provider.get_tor_handler() - self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0])) + self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0][1][0])) self.assertIdentical(h, h1) def test_socksport_endpoint_otherhost(self): @@ -142,7 +142,7 @@ class Tor(unittest.TestCase): ) tor_provider = create_tor_provider(reactor, config) h = tor_provider.get_tor_handler() - self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0])) + self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0][1][0])) self.assertIdentical(h, h1) def test_socksport_bad_endpoint(self): From a348d3d50700fa727121a60f8559b08215a29e93 Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 7 Dec 2019 03:20:01 -0700 Subject: [PATCH 0049/1054] relying on memory/gc bad --- src/allmydata/test/test_storage.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 9f3aee9b8..f5a2b016f 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -1,4 +1,5 @@ import time, os.path, platform, stat, re, json, struct, shutil +import gc from twisted.trial import unittest @@ -553,8 +554,14 @@ class Server(unittest.TestCase): # we abandon the first set, so their provisional allocation should be # returned + + # XXX okay, so the weak-key-dictionary in storage-server is + # basically making an interface to the storage-server that is + # "whenever I drop my object AND the garbage-collector removes + # it, *then* that thing is no longer writing"..? ... yuuuuck. del already del writers + gc.collect() # for pypy's benefit self.failUnlessEqual(len(ss._active_writers), 1) # now we have a provisional allocation of 1001 bytes @@ -567,6 +574,7 @@ class Server(unittest.TestCase): del already2 del writers2 del bw + gc.collect() # for pypy's benefit self.failUnlessEqual(len(ss._active_writers), 0) # this also changes the amount reported as available by call_get_disk_stats From 5071ddf124169a2ec23c3b939f6aa1e04b7a7e7c Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 17 Dec 2019 16:30:34 -0700 Subject: [PATCH 0050/1054] add more 'with' statements --- src/allmydata/test/test_system.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 2b7660705..2108ce9dd 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -543,16 +543,16 @@ class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin): self.clients.append(c) c.set_default_mutable_keysize(TEST_RSA_KEY_SIZE) - f = open(os.path.join(basedirs[0],"private","helper.furl"), "r") - helper_furl = f.read() - f.close() + with open(os.path.join(basedirs[0],"private","helper.furl"), "r") as f: + helper_furl = f.read() + self.helper_furl = helper_furl if self.numclients >= 4: - f = open(os.path.join(basedirs[3], 'tahoe.cfg'), 'ab+') - f.write( - "[client]\n" - "helper.furl = %s\n" % helper_furl) - f.close() + with open(os.path.join(basedirs[3], 'tahoe.cfg'), 'ab+') as f: + f.write( + "[client]\n" + "helper.furl = {}\n".format(helper_furl) + ) # this starts the rest of the clients for i in range(1, self.numclients): From 5175a6c6a011153793612ea1c22ebd425818755e Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 17 Dec 2019 16:31:46 -0700 Subject: [PATCH 0051/1054] asserts for systemexit --- src/allmydata/test/cli/test_start.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/allmydata/test/cli/test_start.py b/src/allmydata/test/cli/test_start.py index b38ca7fd8..90fde3969 100644 --- a/src/allmydata/test/cli/test_start.py +++ b/src/allmydata/test/cli/test_start.py @@ -254,7 +254,11 @@ class RunTests(unittest.TestCase): ]) i, o, e = StringIO(), StringIO(), StringIO() - runner.dispatch(config, i, o, e) + d = runner.dispatch(config, i, o, e) + + def must_be_systemexit(f): + assert isinstance(f.value, SystemExit) + d.addErrback(must_be_systemexit) output = e.getvalue() # should print out the collected logs and an error-code @@ -266,10 +270,6 @@ class RunTests(unittest.TestCase): "Configuration error:", output, ) - # this is SystemExit(0) for some reason I can't understand, - # while running on the command-line, "echo $?" shows "1" on - # this same error (some config exception)... - errs = self.flushLoggedErrors(SystemExit) - self.assertEqual(1, len(errs)) # ensure reactor.stop was actually called self.assertEqual([None], stopped) + return d From d06127c720b31831c444fbba82e1b8e78640fbe9 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 18 Dec 2019 03:54:49 -0700 Subject: [PATCH 0052/1054] news --- newsfragments/1792.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/1792.feature diff --git a/newsfragments/1792.feature b/newsfragments/1792.feature new file mode 100644 index 000000000..b2b839664 --- /dev/null +++ b/newsfragments/1792.feature @@ -0,0 +1 @@ +PyPy is now a supported platform. \ No newline at end of file From 4928d62d661d175a3511db08b799a52527f281b6 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 18 Dec 2019 17:02:40 -0700 Subject: [PATCH 0053/1054] use set instead of WeakKeyDictionary --- src/allmydata/storage/server.py | 8 ++++---- src/allmydata/test/test_storage.py | 20 +++++++------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/allmydata/storage/server.py b/src/allmydata/storage/server.py index eae6fd698..7741e0c18 100644 --- a/src/allmydata/storage/server.py +++ b/src/allmydata/storage/server.py @@ -1,4 +1,4 @@ -import os, re, weakref, struct, time +import os, re, struct, time import six from foolscap.api import Referenceable @@ -67,7 +67,7 @@ class StorageServer(service.MultiService, Referenceable): self.incomingdir = os.path.join(sharedir, 'incoming') self._clean_incomplete() fileutil.make_dirs(self.incomingdir) - self._active_writers = weakref.WeakKeyDictionary() + self._active_writers = set() log.msg("StorageServer created", facility="tahoe.storage") if reserved_space: @@ -302,7 +302,7 @@ class StorageServer(service.MultiService, Referenceable): if self.no_storage: bw.throw_out_all_data = True bucketwriters[shnum] = bw - self._active_writers[bw] = 1 + self._active_writers.add(bw) if limited: remaining_space -= max_space_per_bucket else: @@ -359,7 +359,7 @@ class StorageServer(service.MultiService, Referenceable): def bucket_writer_closed(self, bw, consumed_size): if self.stats_provider: self.stats_provider.count('storage_server.bytes_added', consumed_size) - del self._active_writers[bw] + self._active_writers.remove(bw) def _get_bucket_shares(self, storage_index): """Return a list of (shnum, pathname) tuples for files that hold diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index f5a2b016f..513d92978 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -541,27 +541,22 @@ class Server(unittest.TestCase): OVERHEAD = 3*4 LEASE_SIZE = 4+32+32+4 canary = FakeCanary(True) - already,writers = self.allocate(ss, "vid1", [0,1,2], 1000, canary) + already, writers = self.allocate(ss, "vid1", [0,1,2], 1000, canary) self.failUnlessEqual(len(writers), 3) # now the StorageServer should have 3000 bytes provisionally # allocated, allowing only 2000 more to be claimed self.failUnlessEqual(len(ss._active_writers), 3) # allocating 1001-byte shares only leaves room for one - already2,writers2 = self.allocate(ss, "vid2", [0,1,2], 1001, canary) + already2, writers2 = self.allocate(ss, "vid2", [0,1,2], 1001, canary) self.failUnlessEqual(len(writers2), 1) self.failUnlessEqual(len(ss._active_writers), 4) # we abandon the first set, so their provisional allocation should be # returned - # XXX okay, so the weak-key-dictionary in storage-server is - # basically making an interface to the storage-server that is - # "whenever I drop my object AND the garbage-collector removes - # it, *then* that thing is no longer writing"..? ... yuuuuck. - del already - del writers - gc.collect() # for pypy's benefit + for x in writers.values(): + x.remote_close() self.failUnlessEqual(len(ss._active_writers), 1) # now we have a provisional allocation of 1001 bytes @@ -574,7 +569,6 @@ class Server(unittest.TestCase): del already2 del writers2 del bw - gc.collect() # for pypy's benefit self.failUnlessEqual(len(ss._active_writers), 0) # this also changes the amount reported as available by call_get_disk_stats @@ -582,12 +576,12 @@ class Server(unittest.TestCase): # now there should be ALLOCATED=1001+12+72=1085 bytes allocated, and # 5000-1085=3915 free, therefore we can fit 39 100byte shares - already3,writers3 = self.allocate(ss,"vid3", range(100), 100, canary) + already3, writers3 = self.allocate(ss,"vid3", range(100), 100, canary) self.failUnlessEqual(len(writers3), 39) self.failUnlessEqual(len(ss._active_writers), 39) - del already3 - del writers3 + for x in writers3.values(): + x._disconnected() self.failUnlessEqual(len(ss._active_writers), 0) ss.disownServiceParent() del ss From 9bcc465f7603b06d058b0d6d3c3a0abed3528785 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 18 Dec 2019 17:52:57 -0700 Subject: [PATCH 0054/1054] unused import --- src/allmydata/test/test_storage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 513d92978..7e4eaa3b6 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -1,5 +1,4 @@ import time, os.path, platform, stat, re, json, struct, shutil -import gc from twisted.trial import unittest From 4c3d0ea6cc87edc28f39e73766865245eb59d1d2 Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 21 Dec 2019 00:03:38 -0700 Subject: [PATCH 0055/1054] use 'with open' for more file-opens --- src/allmydata/blacklist.py | 15 ++-- src/allmydata/control.py | 13 +-- src/allmydata/frontends/auth.py | 29 +++--- src/allmydata/scripts/common.py | 26 +++--- src/allmydata/stats.py | 10 +-- src/allmydata/storage/crawler.py | 10 +-- src/allmydata/storage/expirer.py | 16 ++-- src/allmydata/storage/mutable.py | 148 +++++++++++++++---------------- src/allmydata/storage/server.py | 22 +++-- src/allmydata/storage/shares.py | 5 +- src/allmydata/util/configutil.py | 10 +-- src/allmydata/version_checks.py | 24 ++--- 12 files changed, 155 insertions(+), 173 deletions(-) diff --git a/src/allmydata/blacklist.py b/src/allmydata/blacklist.py index 874ff95ca..af1d185d0 100644 --- a/src/allmydata/blacklist.py +++ b/src/allmydata/blacklist.py @@ -34,13 +34,14 @@ class Blacklist(object): try: if self.last_mtime is None or current_mtime > self.last_mtime: self.entries.clear() - for line in open(self.blacklist_fn, "r").readlines(): - line = line.strip() - if not line or line.startswith("#"): - continue - si_s, reason = line.split(None, 1) - si = base32.a2b(si_s) # must be valid base32 - self.entries[si] = reason + with open(self.blacklist_fn, "r") as f: + for line in f.readlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + si_s, reason = line.split(None, 1) + si = base32.a2b(si_s) # must be valid base32 + self.entries[si] = reason self.last_mtime = current_mtime except Exception as e: twisted_log.err(e, "unparseable blacklist file") diff --git a/src/allmydata/control.py b/src/allmydata/control.py index 07802efba..55f6db4f9 100644 --- a/src/allmydata/control.py +++ b/src/allmydata/control.py @@ -19,12 +19,13 @@ def get_memory_usage(): "VmData") stats = {} try: - for line in open("/proc/self/status", "r").readlines(): - name, right = line.split(":",2) - if name in stat_names: - assert right.endswith(" kB\n") - right = right[:-4] - stats[name] = int(right) * 1024 + with open("/proc/self/status", "r") as f: + for line in f.readlines(): + name, right = line.split(":",2) + if name in stat_names: + assert right.endswith(" kB\n") + right = right[:-4] + stats[name] = int(right) * 1024 except: # Probably not on (a compatible version of) Linux stats['VmSize'] = 0 diff --git a/src/allmydata/frontends/auth.py b/src/allmydata/frontends/auth.py index 49647bc60..ab56bf94d 100644 --- a/src/allmydata/frontends/auth.py +++ b/src/allmydata/frontends/auth.py @@ -31,20 +31,21 @@ class AccountFileChecker(object): self.passwords = {} self.pubkeys = {} self.rootcaps = {} - for line in open(abspath_expanduser_unicode(accountfile), "r"): - line = line.strip() - if line.startswith("#") or not line: - continue - name, passwd, rest = line.split(None, 2) - if passwd.startswith("ssh-"): - bits = rest.split() - keystring = " ".join([passwd] + bits[:-1]) - rootcap = bits[-1] - self.pubkeys[name] = keystring - else: - self.passwords[name] = passwd - rootcap = rest - self.rootcaps[name] = rootcap + with open(abspath_expanduser_unicode(accountfile), "r") as f: + for line in f.readlines(): + line = line.strip() + if line.startswith("#") or not line: + continue + name, passwd, rest = line.split(None, 2) + if passwd.startswith("ssh-"): + bits = rest.split() + keystring = " ".join([passwd] + bits[:-1]) + rootcap = bits[-1] + self.pubkeys[name] = keystring + else: + self.passwords[name] = passwd + rootcap = rest + self.rootcaps[name] = rootcap def _avatarId(self, username): return FTPAvatarID(username, self.rootcaps[username]) diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index d3dde9a72..89c1f94d1 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -131,22 +131,22 @@ def get_aliases(nodedir): aliasfile = os.path.join(nodedir, "private", "aliases") rootfile = os.path.join(nodedir, "private", "root_dir.cap") try: - f = open(rootfile, "r") - rootcap = f.read().strip() - if rootcap: - aliases[DEFAULT_ALIAS] = rootcap + with open(rootfile, "r") as f: + rootcap = f.read().strip() + if rootcap: + aliases[DEFAULT_ALIAS] = rootcap except EnvironmentError: pass try: - f = codecs.open(aliasfile, "r", "utf-8") - for line in f.readlines(): - line = line.strip() - if line.startswith("#") or not line: - continue - name, cap = line.split(u":", 1) - # normalize it: remove http: prefix, urldecode - cap = cap.strip().encode('utf-8') - aliases[name] = cap + with codecs.open(aliasfile, "r", "utf-8") as f: + for line in f.readlines(): + line = line.strip() + if line.startswith("#") or not line: + continue + name, cap = line.split(u":", 1) + # normalize it: remove http: prefix, urldecode + cap = cap.strip().encode('utf-8') + aliases[name] = cap except EnvironmentError: pass return aliases diff --git a/src/allmydata/stats.py b/src/allmydata/stats.py index 73e311fe3..34907b1fa 100644 --- a/src/allmydata/stats.py +++ b/src/allmydata/stats.py @@ -250,16 +250,15 @@ class JSONStatsGatherer(StdOutStatsGatherer): self.jsonfile = os.path.join(basedir, "stats.json") if os.path.exists(self.jsonfile): - f = open(self.jsonfile, 'rb') try: - self.gathered_stats = json.load(f) + with open(self.jsonfile, 'rb') as f: + self.gathered_stats = json.load(f) except Exception: print("Error while attempting to load stats file %s.\n" "You may need to restore this file from a backup," " or delete it if no backup is available.\n" % quote_local_unicode_path(self.jsonfile)) raise - f.close() else: self.gathered_stats = {} @@ -272,9 +271,8 @@ class JSONStatsGatherer(StdOutStatsGatherer): def dump_json(self): tmp = "%s.tmp" % (self.jsonfile,) - f = open(tmp, 'wb') - json.dump(self.gathered_stats, f) - f.close() + with open(tmp, 'wb') as f: + json.dump(self.gathered_stats, f) if os.path.exists(self.jsonfile): os.unlink(self.jsonfile) os.rename(tmp, self.jsonfile) diff --git a/src/allmydata/storage/crawler.py b/src/allmydata/storage/crawler.py index 438dd5e31..008619d64 100644 --- a/src/allmydata/storage/crawler.py +++ b/src/allmydata/storage/crawler.py @@ -191,9 +191,8 @@ class ShareCrawler(service.MultiService): # of the last bucket to be processed, or # None if we are sleeping between cycles try: - f = open(self.statefile, "rb") - state = pickle.load(f) - f.close() + with open(self.statefile, "rb") as f: + state = pickle.load(f) except Exception: state = {"version": 1, "last-cycle-finished": None, @@ -230,9 +229,8 @@ class ShareCrawler(service.MultiService): last_complete_prefix = self.prefixes[lcpi] self.state["last-complete-prefix"] = last_complete_prefix tmpfile = self.statefile + ".tmp" - f = open(tmpfile, "wb") - pickle.dump(self.state, f) - f.close() + with open(tmpfile, "wb") as f: + pickle.dump(self.state, f) fileutil.move_into_place(tmpfile, self.statefile) def startService(self): diff --git a/src/allmydata/storage/expirer.py b/src/allmydata/storage/expirer.py index a284d4f74..a13c188bd 100644 --- a/src/allmydata/storage/expirer.py +++ b/src/allmydata/storage/expirer.py @@ -84,9 +84,8 @@ class LeaseCheckingCrawler(ShareCrawler): # initialize history if not os.path.exists(self.historyfile): history = {} # cyclenum -> dict - f = open(self.historyfile, "wb") - pickle.dump(history, f) - f.close() + with open(self.historyfile, "wb") as f: + pickle.dump(history, f) def create_empty_cycle_dict(self): recovered = self.create_empty_recovered_dict() @@ -303,14 +302,14 @@ class LeaseCheckingCrawler(ShareCrawler): # copy() needs to become a deepcopy h["space-recovered"] = s["space-recovered"].copy() - history = pickle.load(open(self.historyfile, "rb")) + with open(self.historyfile, "rb") as f: + history = pickle.load(f) history[cycle] = h while len(history) > 10: oldcycles = sorted(history.keys()) del history[oldcycles[0]] - f = open(self.historyfile, "wb") - pickle.dump(history, f) - f.close() + with open(self.historyfile, "wb") as f: + pickle.dump(history, f) def get_state(self): """In addition to the crawler state described in @@ -379,7 +378,8 @@ class LeaseCheckingCrawler(ShareCrawler): progress = self.get_progress() state = ShareCrawler.get_state(self) # does a shallow copy - history = pickle.load(open(self.historyfile, "rb")) + with open(self.historyfile, "rb") as f: + history = pickle.load(f) state["history"] = history if not progress["cycle-in-progress"]: diff --git a/src/allmydata/storage/mutable.py b/src/allmydata/storage/mutable.py index 37f773c0d..287ed8fb9 100644 --- a/src/allmydata/storage/mutable.py +++ b/src/allmydata/storage/mutable.py @@ -57,8 +57,8 @@ class MutableShareFile(object): self.home = filename if os.path.exists(self.home): # we don't cache anything, just check the magic - f = open(self.home, 'rb') - data = f.read(self.HEADER_SIZE) + with open(self.home, 'rb') as f: + data = f.read(self.HEADER_SIZE) (magic, write_enabler_nodeid, write_enabler, data_length, extra_least_offset) = \ @@ -80,17 +80,17 @@ class MutableShareFile(object): + data_length) assert extra_lease_offset == self.DATA_OFFSET # true at creation num_extra_leases = 0 - f = open(self.home, 'wb') - header = struct.pack(">32s20s32sQQ", - self.MAGIC, my_nodeid, write_enabler, - data_length, extra_lease_offset, - ) - leases = ("\x00"*self.LEASE_SIZE) * 4 - f.write(header + leases) - # data goes here, empty after creation - f.write(struct.pack(">L", num_extra_leases)) - # extra leases go here, none at creation - f.close() + with open(self.home, 'wb') as f: + header = struct.pack( + ">32s20s32sQQ", + self.MAGIC, my_nodeid, write_enabler, + data_length, extra_lease_offset, + ) + leases = ("\x00" * self.LEASE_SIZE) * 4 + f.write(header + leases) + # data goes here, empty after creation + f.write(struct.pack(">L", num_extra_leases)) + # extra leases go here, none at creation def unlink(self): os.unlink(self.home) @@ -261,10 +261,9 @@ class MutableShareFile(object): def get_leases(self): """Yields a LeaseInfo instance for all leases.""" - f = open(self.home, 'rb') - for i, lease in self._enumerate_leases(f): - yield lease - f.close() + with open(self.home, 'rb') as f: + for i, lease in self._enumerate_leases(f): + yield lease def _enumerate_leases(self, f): for i in range(self._get_num_lease_slots(f)): @@ -277,29 +276,26 @@ class MutableShareFile(object): def add_lease(self, lease_info): precondition(lease_info.owner_num != 0) # 0 means "no lease here" - f = open(self.home, 'rb+') - num_lease_slots = self._get_num_lease_slots(f) - empty_slot = self._get_first_empty_lease_slot(f) - if empty_slot is not None: - self._write_lease_record(f, empty_slot, lease_info) - else: - self._write_lease_record(f, num_lease_slots, lease_info) - f.close() + with open(self.home, 'rb+') as f: + num_lease_slots = self._get_num_lease_slots(f) + empty_slot = self._get_first_empty_lease_slot(f) + if empty_slot is not None: + self._write_lease_record(f, empty_slot, lease_info) + else: + self._write_lease_record(f, num_lease_slots, lease_info) def renew_lease(self, renew_secret, new_expire_time): accepting_nodeids = set() - f = open(self.home, 'rb+') - for (leasenum,lease) in self._enumerate_leases(f): - if timing_safe_compare(lease.renew_secret, renew_secret): - # yup. See if we need to update the owner time. - if new_expire_time > lease.expiration_time: - # yes - lease.expiration_time = new_expire_time - self._write_lease_record(f, leasenum, lease) - f.close() - return - accepting_nodeids.add(lease.nodeid) - f.close() + with open(self.home, 'rb+') as f: + for (leasenum,lease) in self._enumerate_leases(f): + if timing_safe_compare(lease.renew_secret, renew_secret): + # yup. See if we need to update the owner time. + if new_expire_time > lease.expiration_time: + # yes + lease.expiration_time = new_expire_time + self._write_lease_record(f, leasenum, lease) + return + accepting_nodeids.add(lease.nodeid) # Return the accepting_nodeids set, to give the client a chance to # update the leases on a share which has been migrated from its # original server to a new one. @@ -333,21 +329,21 @@ class MutableShareFile(object): cancel_secret="\x00"*32, expiration_time=0, nodeid="\x00"*20) - f = open(self.home, 'rb+') - for (leasenum,lease) in self._enumerate_leases(f): - accepting_nodeids.add(lease.nodeid) - if timing_safe_compare(lease.cancel_secret, cancel_secret): - self._write_lease_record(f, leasenum, blank_lease) - modified += 1 - else: - remaining += 1 - if modified: - freed_space = self._pack_leases(f) - f.close() - if not remaining: - freed_space += os.stat(self.home)[stat.ST_SIZE] - self.unlink() - return freed_space + with open(self.home, 'rb+') as f: + for (leasenum,lease) in self._enumerate_leases(f): + accepting_nodeids.add(lease.nodeid) + if timing_safe_compare(lease.cancel_secret, cancel_secret): + self._write_lease_record(f, leasenum, blank_lease) + modified += 1 + else: + remaining += 1 + if modified: + freed_space = self._pack_leases(f) + f.close() + if not remaining: + freed_space += os.stat(self.home)[stat.ST_SIZE] + self.unlink() + return freed_space msg = ("Unable to cancel non-existent lease. I have leases " "accepted by nodeids: ") @@ -372,10 +368,9 @@ class MutableShareFile(object): def readv(self, readv): datav = [] - f = open(self.home, 'rb') - for (offset, length) in readv: - datav.append(self._read_share_data(f, offset, length)) - f.close() + with open(self.home, 'rb') as f: + for (offset, length) in readv: + datav.append(self._read_share_data(f, offset, length)) return datav # def remote_get_length(self): @@ -385,10 +380,9 @@ class MutableShareFile(object): # return data_length def check_write_enabler(self, write_enabler, si_s): - f = open(self.home, 'rb+') - (real_write_enabler, write_enabler_nodeid) = \ - self._read_write_enabler_and_nodeid(f) - f.close() + with open(self.home, 'rb+') as f: + (real_write_enabler, write_enabler_nodeid) = \ + self._read_write_enabler_and_nodeid(f) # avoid a timing attack #if write_enabler != real_write_enabler: if not timing_safe_compare(write_enabler, real_write_enabler): @@ -405,27 +399,25 @@ class MutableShareFile(object): def check_testv(self, testv): test_good = True - f = open(self.home, 'rb+') - for (offset, length, operator, specimen) in testv: - data = self._read_share_data(f, offset, length) - if not testv_compare(data, operator, specimen): - test_good = False - break - f.close() + with open(self.home, 'rb+') as f: + for (offset, length, operator, specimen) in testv: + data = self._read_share_data(f, offset, length) + if not testv_compare(data, operator, specimen): + test_good = False + break return test_good def writev(self, datav, new_length): - f = open(self.home, 'rb+') - for (offset, data) in datav: - self._write_share_data(f, offset, data) - if new_length is not None: - cur_length = self._read_data_length(f) - if new_length < cur_length: - self._write_data_length(f, new_length) - # TODO: if we're going to shrink the share file when the - # share data has shrunk, then call - # self._change_container_size() here. - f.close() + with open(self.home, 'rb+') as f: + for (offset, data) in datav: + self._write_share_data(f, offset, data) + if new_length is not None: + cur_length = self._read_data_length(f) + if new_length < cur_length: + self._write_data_length(f, new_length) + # TODO: if we're going to shrink the share file when the + # share data has shrunk, then call + # self._change_container_size() here. def testv_compare(a, op, b): assert op in ("lt", "le", "eq", "ne", "ge", "gt") diff --git a/src/allmydata/storage/server.py b/src/allmydata/storage/server.py index 7741e0c18..5cc4be95c 100644 --- a/src/allmydata/storage/server.py +++ b/src/allmydata/storage/server.py @@ -317,9 +317,8 @@ class StorageServer(service.MultiService, Referenceable): def _iter_share_files(self, storage_index): for shnum, filename in self._get_bucket_shares(storage_index): - f = open(filename, 'rb') - header = f.read(32) - f.close() + with open(filename, 'rb') as f: + header = f.read(32) if header[:32] == MutableShareFile.MAGIC: sf = MutableShareFile(filename, self) # note: if the share has been migrated, the renew_lease() @@ -682,15 +681,14 @@ class StorageServer(service.MultiService, Referenceable): # windows can't handle colons in the filename fn = os.path.join(self.corruption_advisory_dir, "%s--%s-%d" % (now, si_s, shnum)).replace(":","") - f = open(fn, "w") - f.write("report: Share Corruption\n") - f.write("type: %s\n" % share_type) - f.write("storage_index: %s\n" % si_s) - f.write("share_number: %d\n" % shnum) - f.write("\n") - f.write(reason) - f.write("\n") - f.close() + with open(fn, "w") as f: + f.write("report: Share Corruption\n") + f.write("type: %s\n" % share_type) + f.write("storage_index: %s\n" % si_s) + f.write("share_number: %d\n" % shnum) + f.write("\n") + f.write(reason) + f.write("\n") log.msg(format=("client claims corruption in (%(share_type)s) " + "%(si)s-%(shnum)d: %(reason)s"), share_type=share_type, si=si_s, shnum=shnum, reason=reason, diff --git a/src/allmydata/storage/shares.py b/src/allmydata/storage/shares.py index 558bddc19..bd94c0f3f 100644 --- a/src/allmydata/storage/shares.py +++ b/src/allmydata/storage/shares.py @@ -4,9 +4,8 @@ from allmydata.storage.mutable import MutableShareFile from allmydata.storage.immutable import ShareFile def get_share_file(filename): - f = open(filename, "rb") - prefix = f.read(32) - f.close() + with open(filename, "rb") as f: + prefix = f.read(32) if prefix == MutableShareFile.MAGIC: return MutableShareFile(filename) # otherwise assume it's immutable diff --git a/src/allmydata/util/configutil.py b/src/allmydata/util/configutil.py index d58bc4217..3699db35d 100644 --- a/src/allmydata/util/configutil.py +++ b/src/allmydata/util/configutil.py @@ -13,15 +13,12 @@ class UnknownConfigError(Exception): def get_config(tahoe_cfg): config = SafeConfigParser() - f = open(tahoe_cfg, "rb") - try: + with open(tahoe_cfg, "rb") as f: # Skip any initial Byte Order Mark. Since this is an ordinary file, we # don't need to handle incomplete reads, and can assume seekability. if f.read(3) != '\xEF\xBB\xBF': f.seek(0) config.readfp(f) - finally: - f.close() return config def set_config(config, section, option, value): @@ -31,11 +28,8 @@ def set_config(config, section, option, value): assert config.get(section, option) == value def write_config(tahoe_cfg, config): - f = open(tahoe_cfg, "wb") - try: + with open(tahoe_cfg, "wb") as f: config.write(f) - finally: - f.close() def validate_config(fname, cfg, valid_config): """ diff --git a/src/allmydata/version_checks.py b/src/allmydata/version_checks.py index 9f084ff4b..aa42c2eee 100644 --- a/src/allmydata/version_checks.py +++ b/src/allmydata/version_checks.py @@ -235,18 +235,18 @@ def _get_linux_distro(): return (_distname, _version) try: - etclsbrel = open("/etc/lsb-release", "rU") - for line in etclsbrel: - m = _distributor_id_file_re.search(line) - if m: - _distname = m.group(1).strip() - if _distname and _version: - return (_distname, _version) - m = _release_file_re.search(line) - if m: - _version = m.group(1).strip() - if _distname and _version: - return (_distname, _version) + with open("/etc/lsb-release", "rU") as etclsbrel: + for line in etclsbrel: + m = _distributor_id_file_re.search(line) + if m: + _distname = m.group(1).strip() + if _distname and _version: + return (_distname, _version) + m = _release_file_re.search(line) + if m: + _version = m.group(1).strip() + if _distname and _version: + return (_distname, _version) except EnvironmentError: pass From 8965c77d6358a11ccc368b34d2708bd78464e0e8 Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 21 Dec 2019 00:04:05 -0700 Subject: [PATCH 0056/1054] look for correct error-string --- src/allmydata/test/test_backupdb.py | 3 +-- src/allmydata/test/test_multi_introducers.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/test_backupdb.py b/src/allmydata/test/test_backupdb.py index b908292b2..a10c7514b 100644 --- a/src/allmydata/test/test_backupdb.py +++ b/src/allmydata/test/test_backupdb.py @@ -60,8 +60,7 @@ class BackupDB(unittest.TestCase): bdb = backupdb.get_backupdb(where, stderr_f) self.failUnlessEqual(bdb, None) stderr = stderr_f.getvalue() - self.failUnlessIn("Unable to create/open backupdb file %s" % (where,), stderr) - self.failUnlessIn("unable to open database file", stderr) + self.failUnlessIn("Could not open database", stderr) def writeto(self, filename, data): diff --git a/src/allmydata/test/test_multi_introducers.py b/src/allmydata/test/test_multi_introducers.py index 4c37cb5b0..2c1e933fa 100644 --- a/src/allmydata/test/test_multi_introducers.py +++ b/src/allmydata/test/test_multi_introducers.py @@ -159,7 +159,7 @@ class NoDefault(unittest.TestCase): yield create_client(self.basedir) self.assertEquals( str(ctx.exception), - "string indices must be integers", + "string index must be an integer, not str", ) @defer.inlineCallbacks From f8117320cb6b0748f8f1c066fa8b52430d17fc0b Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 21 Dec 2019 00:04:27 -0700 Subject: [PATCH 0057/1054] tests which rely on memory-reclamation need gc.collect() on pypy --- src/allmydata/test/test_util.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index 9a576870a..ca05e4008 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -8,6 +8,7 @@ import six import hashlib import os, time, sys import yaml +import gc # support PyPy from six.moves import StringIO from datetime import timedelta @@ -1303,9 +1304,11 @@ class CacheDir(unittest.TestCase): a = cdm.get_file("a") b = cdm.get_file("b") c = cdm.get_file("c") - f = open(a.get_filename(), "wb"); f.write("hi"); f.close(); del f - f = open(b.get_filename(), "wb"); f.write("hi"); f.close(); del f - f = open(c.get_filename(), "wb"); f.write("hi"); f.close(); del f + for x in {a, b, c}: + with open(x.get_filename(), "wb") as f: + f.write("hi") + del x + gc.collect() # for PyPy _failUnlessExists("a") _failUnlessExists("b") @@ -1318,6 +1321,7 @@ class CacheDir(unittest.TestCase): _failUnlessExists("c") del a + gc.collect() # for PyPy # this file won't be deleted yet, because it isn't old enough cdm.check() _failUnlessExists("a") @@ -1335,6 +1339,7 @@ class CacheDir(unittest.TestCase): cdm.old = 60*60 del b + gc.collect() # for PyPy cdm.check() _failIfExists("a") @@ -1348,6 +1353,7 @@ class CacheDir(unittest.TestCase): _failUnlessExists("b") _failUnlessExists("c") del b2 + gc.collect() # for PyPy ctr = [0] class EqButNotIs(object): @@ -1617,6 +1623,7 @@ class Pipeline(unittest.TestCase): self.calls[2][0].errback(ValueError("three-error")) del d1,d2,d3,d4 + gc.collect() # for PyPy class SampleError(Exception): pass From 29563b8f2dd809cda82628aad96d070864e90d8d Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 21 Dec 2019 18:01:42 -0700 Subject: [PATCH 0058/1054] assert proper errors --- src/allmydata/test/test_backupdb.py | 2 +- src/allmydata/test/test_multi_introducers.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/allmydata/test/test_backupdb.py b/src/allmydata/test/test_backupdb.py index a10c7514b..03ab62e8f 100644 --- a/src/allmydata/test/test_backupdb.py +++ b/src/allmydata/test/test_backupdb.py @@ -60,7 +60,7 @@ class BackupDB(unittest.TestCase): bdb = backupdb.get_backupdb(where, stderr_f) self.failUnlessEqual(bdb, None) stderr = stderr_f.getvalue() - self.failUnlessIn("Could not open database", stderr) + self.failUnlessIn("unable to open database file", stderr) def writeto(self, filename, data): diff --git a/src/allmydata/test/test_multi_introducers.py b/src/allmydata/test/test_multi_introducers.py index 2c1e933fa..34e6e5d96 100644 --- a/src/allmydata/test/test_multi_introducers.py +++ b/src/allmydata/test/test_multi_introducers.py @@ -157,9 +157,9 @@ class NoDefault(unittest.TestCase): self.yaml_path.setContent(EQUALS_YAML) with self.assertRaises(TypeError) as ctx: yield create_client(self.basedir) - self.assertEquals( - str(ctx.exception), - "string index must be an integer, not str", + self.assertIsInstance( + ctx.exception, + TypeError, ) @defer.inlineCallbacks From bddd2cc9961d6c6a429c9f8ec9a81769725ce6af Mon Sep 17 00:00:00 2001 From: meejah Date: Sat, 21 Dec 2019 22:03:24 -0700 Subject: [PATCH 0059/1054] disable system tests on PyPy --- src/allmydata/test/test_system.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 2108ce9dd..66f460695 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -422,6 +422,9 @@ def _render_section_values(values): class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin): def setUp(self): + if 'pypy' in sys.version.lower(): + self.skip = "pypy can't run these, due to SSL errors (ee key too tiny)" + self.port_assigner = SameProcessStreamEndpointAssigner() self.port_assigner.setUp() self.addCleanup(self.port_assigner.tearDown) From 13350d60c2905a79c313edd4dbd984ed2494dec2 Mon Sep 17 00:00:00 2001 From: meejah Date: Sun, 22 Dec 2019 02:21:55 -0700 Subject: [PATCH 0060/1054] assertFailure instead of bespoke code --- src/allmydata/test/cli/test_start.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/allmydata/test/cli/test_start.py b/src/allmydata/test/cli/test_start.py index 90fde3969..42c70f024 100644 --- a/src/allmydata/test/cli/test_start.py +++ b/src/allmydata/test/cli/test_start.py @@ -256,9 +256,7 @@ class RunTests(unittest.TestCase): i, o, e = StringIO(), StringIO(), StringIO() d = runner.dispatch(config, i, o, e) - def must_be_systemexit(f): - assert isinstance(f.value, SystemExit) - d.addErrback(must_be_systemexit) + self.assertFailure(d, SystemExit) output = e.getvalue() # should print out the collected logs and an error-code From 00373fc21159ff55c47b208329fd75f1b5b61f25 Mon Sep 17 00:00:00 2001 From: meejah Date: Mon, 30 Dec 2019 20:25:59 -0700 Subject: [PATCH 0061/1054] special-case pypy --- src/allmydata/test/test_backupdb.py | 8 ++++++-- src/allmydata/test/test_system.py | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/allmydata/test/test_backupdb.py b/src/allmydata/test/test_backupdb.py index 03ab62e8f..858640d62 100644 --- a/src/allmydata/test/test_backupdb.py +++ b/src/allmydata/test/test_backupdb.py @@ -1,4 +1,4 @@ - +import sys import os.path, time from six.moves import cStringIO as StringIO from twisted.trial import unittest @@ -60,7 +60,11 @@ class BackupDB(unittest.TestCase): bdb = backupdb.get_backupdb(where, stderr_f) self.failUnlessEqual(bdb, None) stderr = stderr_f.getvalue() - self.failUnlessIn("unable to open database file", stderr) + # the error-message is different under PyPy ... not sure why? + if 'pypy' in sys.version.lower(): + self.failUnlessIn("Could not open database", stderr) + else: + self.failUnlessIn("unable to open database file", stderr) def writeto(self, filename, data): diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 66f460695..3b8d2b614 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -421,10 +421,10 @@ def _render_section_values(values): class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin): - def setUp(self): - if 'pypy' in sys.version.lower(): - self.skip = "pypy can't run these, due to SSL errors (ee key too tiny)" + if 'pypy' in sys.version.lower(): + skip = "pypy can't run these, due to SSL errors (ee key too tiny)" + def setUp(self): self.port_assigner = SameProcessStreamEndpointAssigner() self.port_assigner.setUp() self.addCleanup(self.port_assigner.tearDown) From 1e039d8364e3a74a43af613f5e931e0689d90005 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 1 Jan 2020 18:59:40 -0700 Subject: [PATCH 0062/1054] certs big enough to make pypy happy --- src/allmydata/test/test_system.py | 809 +++++++++++++++++++----------- 1 file changed, 517 insertions(+), 292 deletions(-) diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 3b8d2b614..b3b82582e 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -53,333 +53,561 @@ enough to not fit inside a LIT uri. # overhead of key generation SYSTEM_TEST_CERTS = [ """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQyMDVaFw0wOTA3MjUyMjQyMDVaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxHCWajrR -2h/iurw8k93m8WUdE3xypJiiAITw7GkKlKbCLD+dEce2MXwVVYca0n/MZZsj89Cu -Ko0lLjksMseoSDoj98iEmVpaY5mc2ntpQ+FXdoEmPP234XRWEg2HQ+EaK6+WkGQg -DDXQvFJCVCQk/n1MdAwZZ6vqf2ITzSuD44kCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQBn6qPKGdFjWJy7sOOTUFfm/THhHQqAh1pBDLkjR+OtzuobCoP8n8J1LNG3Yxds -Jj7NWQL7X5TfOlfoi7e9jK0ujGgWh3yYU6PnHzJLkDiDT3LCSywQuGXCjh0tOStS -2gaCmmAK2cfxSStKzNcewl2Zs8wHMygq8TLFoZ6ozN1+xQ== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1iNV +z07PYwZwucl87QlL2TFZvDxD4flZ/p3BZE3DCT5Efn9w2NT4sHXL1e+R/qsDFuNG +bw1y1TRM0DGK6Wr0XRT2mLQULNgB8y/HrhcSdONsYRyWdj+LimyECKjwh0iSkApv +Yj/7IOuq6dOoh67YXPdf75OHLShm4+8q8fuwhBL+nuuO4NhZDJKupYHcnuCkcF88 +LN77HKrrgbpyVmeghUkwJMLeJCewvYVlambgWRiuGGexFgAm6laS3rWetOcdm9eg +FoA9PKNN6xvPatbj99MPoLpBbzsI64M0yT/wTSw1pj/Nom3rwfMa2OH8Kk7c8R/r +U3xj4ZY1DTlGERvejQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAwyQjQ3ZgtJ3JW +r3/EPdqSUBamTfXIpOh9rXmRjPpbe+MvenqIzl4q+GnkL5mdEb1e1hdKQZgFQ5Q5 +tbcNIz6h5C07KaNtbqhZCx5c/RUEH87VeXuAuOqZHbZWJ18q0tnk+YgWER2TOkgE +RI2AslcsJBt88UUOjHX6/7J3KjPFaAjW1QV3TTsHxk14aYDYJwPdz+ijchgbOPQ0 +i+ilhzcB+qQnOC1s4xQSFo+zblTO7EgqM9KpupYfOVFh46P1Mak2W8EDvhz0livl +OROXJ6nR/13lmQdfVX6T45d+ITBwtmW2nGAh3oI3JlArGKHaW+7qnuHR72q9FSES +cEYA/wmk -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQDEcJZqOtHaH+K6vDyT3ebxZR0TfHKkmKIAhPDsaQqUpsIsP50R -x7YxfBVVhxrSf8xlmyPz0K4qjSUuOSwyx6hIOiP3yISZWlpjmZzae2lD4Vd2gSY8 -/bfhdFYSDYdD4Rorr5aQZCAMNdC8UkJUJCT+fUx0DBlnq+p/YhPNK4PjiQIDAQAB -AoGAZyDMdrymiyMOPwavrtlicvyohSBid3MCKc+hRBvpSB0790r2RO1aAySndp1V -QYmCXx1RhKDbrs8m49t0Dryu5T+sQrFl0E3usAP3vvXWeh4jwJ9GyiRWy4xOEuEQ -3ewjbEItHqA/bRJF0TNtbOmZTDC7v9FRPf2bTAyFfTZep5kCQQD33q1RA8WUYtmQ -IArgHqt69i421lpXlOgqotFHwTx4FiGgVzDQCDuXU6txB9EeKRM340poissav/n6 -bkLZ7/VDAkEAyuIPkeI59sE5NnmW+N47NbCfdM1Smy1YxZpv942EmP9Veub5N0dw -iK5bLAgEguUIjpTsh3BRmsE9Xd+ItmnRQwJBAMZhbg19G1EbnE0BmDKv2UbcaThy -bnPSNc6J6T2opqDl9ZvCrMqTDD6dNIWOYAvni/4a556sFsoeBBAu10peBskCQE6S -cB86cuJagLLVMh/dySaI6ahNoFFSpY+ZuQUxfInYUR2Q+DFtbGqyw8JwtHaRBthZ -WqU1XZVGg2KooISsxIsCQQD1PS7//xHLumBb0jnpL7n6W8gmiTyzblT+0otaCisP -fN6rTlwV1o8VsOUAz0rmKO5RArCbkmb01WtMgPCDBYkk ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWI1XPTs9jBnC5 +yXztCUvZMVm8PEPh+Vn+ncFkTcMJPkR+f3DY1PiwdcvV75H+qwMW40ZvDXLVNEzQ +MYrpavRdFPaYtBQs2AHzL8euFxJ042xhHJZ2P4uKbIQIqPCHSJKQCm9iP/sg66rp +06iHrthc91/vk4ctKGbj7yrx+7CEEv6e647g2FkMkq6lgdye4KRwXzws3vscquuB +unJWZ6CFSTAkwt4kJ7C9hWVqZuBZGK4YZ7EWACbqVpLetZ605x2b16AWgD08o03r +G89q1uP30w+gukFvOwjrgzTJP/BNLDWmP82ibevB8xrY4fwqTtzxH+tTfGPhljUN +OUYRG96NAgMBAAECggEAJ5xztBx0+nFnisZ9yG8uy6d4XPyc5gE1J4dRDdfgmyYc +j3XNjx6ePi4cHZ/qVryVnrc+AS7wrgW1q9FuS81QFKPbFdZB4SW3/p85BbgY3uxu +0Ovz3T3V9y4polx12eCP0/tKLVd+gdF2VTik9Sxfs5rC8VNN7wmJNuK4A/k15sgy +BIu/R8NlMNGQySNhtccp+dzB8uTyKx5zFZhVvnAK/3YX9BC2V4QBW9JxO4S8N0/9 +48e9Sw/fGCfQ/EFPKGCvTvfuRqJ+4t5k10FygXJ+s+y70ifYi+aSsjJBuranbLJp +g5TwhuKnTWs8Nth3YRLbcJL4VBIOehjAWy8pDMMtlQKBgQD0O8cHb8cOTGW0BijC +NDofhA2GooQUUR3WL324PXWZq0DXuBDQhJVBKWO3AYonivhhd/qWO8lea9MEmU41 +nKZ7maS4B8AJLJC08P8GL1uCIE/ezEXEi9JwC1zJiyl595Ap4lSAozH0DwjNvmGL +5mIdYg0BliqFXbloNJkNlb7INwKBgQDgdGEIWXc5Y1ncWNs6iDIV/t2MlL8vLrP0 +hpkl/QiMndOQyD6JBo0+ZqvOQTSS4NTSxBROjPxvFbEJ3eH8Pmn8gHOf46fzP1OJ +wlYv0gYzkN4FE/tN6JnO2u9pN0euyyZLM1fnEcrMWColMN8JlWjtA7Gbxm8lkfa4 +3vicaJtlWwKBgQCQYL4ZgVR0+Wit8W4qz+EEPHYafvwBXqp6sXxqa7qXawtb+q3F +9nqdGLCfwMNA+QA37ksugI1byfXmpBH902r/aiZbvAkj4zpwHH9F0r0PwbY1iSA9 +PkLahX0Gj8OnHFgWynsVyGOBWVnk9oSHxVt+7zWtGG5uhKdUGLPZugocJQKBgB61 +7bzduOFiRZ5PjhdxISE/UQL2Kz6Cbl7rt7Kp72yF/7eUnnHTMqoyFBnRdCcQmi4I +ZBrnUXbFigamlFAWHhxNWwSqeoVeychUjcRXQT/291nMhRsA02KpNA66YJV6+E9b +xBA6r/vLqGCUUkAWcFfVpIyC1xxV32MmJvAHpBN3AoGAPF3MUFiO0iKNZfst6Tm3 +rzrldLawDo98DRZ7Yb2kWlWZYqUk/Nvryvo2cns75WGSMDYVbbRp+BY7kZmNYa9K +iQzKDL54ZRu6V+getJdeAO8yXoCmnZKxt5OHvOSrQMfAmFKSwLwxBbZBfXEyuune +yfusXLtCgajpreoVIa0xWdQ= +-----END PRIVATE KEY----- """, # 0 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQyMDVaFw0wOTA3MjUyMjQyMDVaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAs9CALdmW -kJ6r0KPSLdGCA8rzQKxWayrMckT22ZtbRv3aw6VA96dWclpY+T2maV0LrAzmMSL8 -n61ydJHM33iYDOyWbwHWN45XCjY/e20PL54XUl/DmbBHEhQVQLIfCldcRcnWEfoO -iOhDJfWpDO1dmP/aOYLdkZCZvBtPAfyUqRcCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQAN9eaCREkzzk4yPIaWYkWHg3Igs1vnOR/iDw3OjyxO/xJFP2lkA2WtrwL2RTRq -dxA8gwdPyrWgdiZElwZH8mzTJ4OdUXLSMclLOg9kvH6gtSvhLztfEDwDP1wRhikh -OeWWu2GIC+uqFCI1ftoGgU+aIa6yrHswf66rrQvBSSvJPQ== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApDzW +4ZBeK9w4xpRaed6lXzeCO0Xmr3f0ynbueSdiZ89FWoAMgK+SiBIOViYV6hfm0Wah +lemSNzFGx5LvDSg2uwSqEP23DeM9O/SQPgIAiLeeEsYZJcgg2jz92YfFEaahsGdI +6qSP4XI2/5dgKRpPOYDGyw6R5PQR6w22Xq1WD1jBvImk/k09I9jHRn40pYbaJzbg +U2aIjvOruo2kqe4f6iDqE0piYimAZJUvemu1UoyV5NG590hGkDuWsMD77+d2FxCj +9Nzb+iuuG3ksnanHPyXi1hQmzp5OmzVWaevCHinNjWgsuSuLGO9H2SLf3wwp2UCs +EpKtzoKrnZdEg/anNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQChxtr67o1aZZMJ +A6gESPtFjZLw6wG0j50JsrWKLvoXVts1ToJ9u2nx01aFKjBwb4Yg+vdJfDgIIAEm +jS56h6H2DfJlkTWHmi8Vx1wuusWnrNwYMI53tdlRIpD2+Ne7yeoLQZcVN2wuPmxD +Mbksg4AI4csmbkU/NPX5DtMy4EzM/pFvIcxNIVRUMVTFzn5zxhKfhyPqrMI4fxw1 +UhUbEKO+QgIqTNp/dZ0lTbFs5HJQn6yirWyyvQKBPmaaK+pKd0RST/T38OU2oJ/J +LojRs7ugCJ+bxJqegmQrdcVqZZGbpYeK4O/5eIn8KOlgh0nUza1MyjJJemgBBWf7 +HoXB8Fge -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQCz0IAt2ZaQnqvQo9It0YIDyvNArFZrKsxyRPbZm1tG/drDpUD3 -p1ZyWlj5PaZpXQusDOYxIvyfrXJ0kczfeJgM7JZvAdY3jlcKNj97bQ8vnhdSX8OZ -sEcSFBVAsh8KV1xFydYR+g6I6EMl9akM7V2Y/9o5gt2RkJm8G08B/JSpFwIDAQAB -AoGBAIUy5zCPpSP+FeJY6CG+t6Pdm/IFd4KtUoM3KPCrT6M3+uzApm6Ny9Crsor2 -qyYTocjSSVaOxzn1fvpw4qWLrH1veUf8ozMs8Z0VuPHD1GYUGjOXaBPXb5o1fQL9 -h7pS5/HrDDPN6wwDNTsxRf/fP58CnfwQUhwdoxcx8TnVmDQxAkEA6N3jBXt/Lh0z -UbXHhv3QBOcqLZA2I4tY7wQzvUvKvVmCJoW1tfhBdYQWeQv0jzjL5PzrrNY8hC4l -8+sFM3h5TwJBAMWtbFIEZfRSG1JhHK3evYHDTZnr/j+CdoWuhzP5RkjkIKsiLEH7 -2ZhA7CdFQLZF14oXy+g1uVCzzfB2WELtUbkCQQDKrb1XWzrBlzbAipfkXWs9qTmj -uJ32Z+V6+0xRGPOXxJ0sDDqw7CeFMfchWg98zLFiV+SEZV78qPHtkAPR3ayvAkB+ -hUMhM4N13t9x2IoclsXAOhp++9bdG0l0woHyuAdOPATUw6iECwf4NQVxFRgYEZek -4Ro3Y7taddrHn1dabr6xAkAic47OoLOROYLpljmJJO0eRe3Z5IFe+0D2LfhAW3LQ -JU+oGq5pCjfnoaDElRRZn0+GmunnWeQEYKoflTi/lI9d ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkPNbhkF4r3DjG +lFp53qVfN4I7Reavd/TKdu55J2Jnz0VagAyAr5KIEg5WJhXqF+bRZqGV6ZI3MUbH +ku8NKDa7BKoQ/bcN4z079JA+AgCIt54SxhklyCDaPP3Zh8URpqGwZ0jqpI/hcjb/ +l2ApGk85gMbLDpHk9BHrDbZerVYPWMG8iaT+TT0j2MdGfjSlhtonNuBTZoiO86u6 +jaSp7h/qIOoTSmJiKYBklS96a7VSjJXk0bn3SEaQO5awwPvv53YXEKP03Nv6K64b +eSydqcc/JeLWFCbOnk6bNVZp68IeKc2NaCy5K4sY70fZIt/fDCnZQKwSkq3Ogqud +l0SD9qc3AgMBAAECggEBAIu55uaIOFYASZ1IYaEFNpRHWVisI5Js76nAfSo9w46l +3E8eWYSx2mxBUEkipco/A3RraFVuHaMvHRR1gUMkT0vUsAs8jxwVk+cKLh1S/rlR +3f4C4yotlSWWdjE3PQXDShQWCwb1ciNPVFMmqfzOEVDOqlHe12h97TCYverWdT0f +3LZICLQsZd1WPKnPNXrsRRDCBuRLapdg+M0oJ+y6IiCdm+qM7Qvaoef6hlvm5ECz +LCM92db5BKTuPOQXMx2J8mjaBgU3aHxRV08IFgs7mI6q0t0FM7LlytIAJq1Hg5QU +36zDKo8tblkPijWZWlqlZCnlarrd3Ar/BiLEiuOGDMECgYEA1GOp0KHy0mbJeN13 ++TDsgP7zhmqFcuJREu2xziNJPK2S06NfGYE8vuVqBGzBroLTQ3dK7rOJs9C6IjCE +mH7ZeHzfcKohpZnl443vHMSpgdh/bXTEO1aQZNbJ2hLYs8ie/VqqHR0u6YtpUqZL +LgaUA0U8GnlsO55B8kyCelckmDkCgYEAxfYQMPEEzg1tg2neqEfyoeY0qQTEJTeh +CPMztowSJpIyF1rQH6TaG0ZchkiAkw3W58RVDfvK72TuVlC5Kz00C2/uPnrqm0dX +iMPeML5rFlG3VGCrSTnAPI+az6P65q8zodqcTtA8xoxgPOlc/lINOxiTEMxLyeGF +8GyP+sCM2u8CgYEAvMBR05OJnEky9hJEpBZBqSZrQGL8dCwDh0HtCdi8JovPd/yx +8JW1aaWywXnx6uhjXoru8hJm54IxWV8rB+d716OKY7MfMfACqWejQDratgW0wY7L +MjztGGD2hLLJGYXLHjfsBPHBllaKZKRbHe1Er19hWdndQWKVEwPB1X4KjKkCgYEA +nWHmN3K2djbYtRyLR1CEBtDlVuaSJmCWp23q1BuCJqYeKtEpG69NM1f6IUws5Dyh +eXtuf4KKMU8V6QueW1D6OomPaJ8CO9c5MWM/F5ObwY/P58Y/ByVhvwQQeToONC5g +JzKNCF+nodZigKqrIwoKuMvtx/IT4vloKd+1jA5fLYMCgYBoT3HLCyATVdDSt1TZ +SbEDoLSYt23KRjQV93+INP949dYCagtgh/kTzxBopw5FljISLfdYizIRo2AzhhfP +WWpILlnt19kD+sNirJVqxJacfEZsu5baWTedI/yrCuVsAs/s3/EEY6q0Qywknxtp +Fwh1/8y5t14ib5fxOVhi8X1nEA== +-----END PRIVATE KEY----- """, # 1 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQyMDZaFw0wOTA3MjUyMjQyMDZaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsxG7LTrz -DF+9wegOR/BRJhjSumPUbYQnNAUKtPraFsGjAJILP44AHdnHt1MONLgTeX1ynapo -q6O/q5cdKtBB7uEh7FpkLCCwpZt/m0y79cynn8AmWoQVgl8oS0567UmPeJnTzFPv -dmT5dlaQALeX5YGceAsEvhmAsdOMttaor38CAwEAATANBgkqhkiG9w0BAQQFAAOB -gQA345rxotfvh2kfgrmRzAyGewVBV4r23Go30GSZir8X2GoH3qKNwO4SekAohuSw -AiXzLUbwIdSRSqaLFxSC7Duqc9eIeFDAWjeEmpfFLBNiw3K8SLA00QrHCUXnECTD -b/Kk6OGuvPOiuuONVjEuEcRdCH3/Li30D0AhJaMynjhQJQ== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwMTn +hXnpKHGAir3WYbOxefVrMA07OZNAsNa29nBwLA+NVIJNUFgquibMj7QYo8+M45oY +6LKr4yRcBryZVvyxfdr92xp8+kLeVApk2WLjkdBTRagHh9qdrY0hQmagCBN6/hLG +Xug8VksQUdhX3vu6ZyMvTLfKRkDOMRVkRGRGg/dOcvom7zpqMCGYenMG2FStr6UV +3s3dlCSZZTdTX5Uoq6yfUUJE3nITGKjpnpJKqIs3PWCIxdj7INIcjJKvIdUcavIV +2hEhh60A8ltmtdpQAXVBE+U7aZgS1fGAWS2A0a3UwuP2pkQp6OyKCUVHpZatbl9F +ahDN2QBzegv/rdJ1zwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAl4OQZ+FB9ZSUv +FL/KwLNt+ONU8Sve/xiX+8vKAvgKm2FrjyK+AZPwibnu+FSt2G4ndZBx4Wvpe5V+ +gCsbzSXlh9cDn2SRXyprt2l/8Fj4eUMaThmLKOK200/N/s2SpmBtnuflBrhNaJpw +DEi2KEPuXsgvkuVzXN06j75cUHwn5LeWDAh0RalkVuGbEWBoFx9Hq8WECdlCy0YS +y09+yO01qz70y88C2rPThKw8kP4bX8aFZbvsnRHsLu/8nEQNlrELcfBarPVHjJ/9 +imxOdymJkV152V58voiXP/PwXhynctQbF7e+0UZ+XEGdbAbZA0BMl7z+b09Z+jF2 +afm4mVox -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQCzEbstOvMMX73B6A5H8FEmGNK6Y9RthCc0BQq0+toWwaMAkgs/ -jgAd2ce3Uw40uBN5fXKdqmiro7+rlx0q0EHu4SHsWmQsILClm3+bTLv1zKefwCZa -hBWCXyhLTnrtSY94mdPMU+92ZPl2VpAAt5flgZx4CwS+GYCx04y21qivfwIDAQAB -AoGBAIlhFg/aRPL+VM9539LzHN60dp8GzceDdqwjHhbAySZiQlLCuJx2rcI4/U65 -CpIJku9G/fLV9N2RkA/trDPXeGyqCTJfnNzyZcvvMscRMFqSGyc21Y0a+GS8bIxt -1R2B18epSVMsWSWWMypeEgsfv29LV7oSWG8UKaqQ9+0h63DhAkEA4i2L/rori/Fb -wpIBfA+xbXL/GmWR7xPW+3nG3LdLQpVzxz4rIsmtO9hIXzvYpcufQbwgVACyMmRf -TMABeSDM7wJBAMquEdTaVXjGfH0EJ7z95Ys2rYTiCXjBfyEOi6RXXReqV9SXNKlN -aKsO22zYecpkAjY1EdUdXWP/mNVEybjpZnECQQCcuh0JPS5RwcTo9c2rjyBOjGIz -g3B1b5UIG2FurmCrWe6pgO3ZJFEzZ/L2cvz0Hj5UCa2JKBZTDvRutZoPumfnAkAb -nSW+y1Rz1Q8m9Ub4v9rjYbq4bRd/RVWtyk6KQIDldYbr5wH8wxgsniSVKtVFFuUa -P5bDY3HS6wMGo42cTOhxAkAcdweQSQ3j7mfc5vh71HeAC1v/VAKGehGOUdeEIQNl -Sb2WuzpZkbfsrVzW6MdlgY6eE7ufRswhDPLWPC8MP0d1 ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAxOeFeekocYCK +vdZhs7F59WswDTs5k0Cw1rb2cHAsD41Ugk1QWCq6JsyPtBijz4zjmhjosqvjJFwG +vJlW/LF92v3bGnz6Qt5UCmTZYuOR0FNFqAeH2p2tjSFCZqAIE3r+EsZe6DxWSxBR +2Ffe+7pnIy9Mt8pGQM4xFWREZEaD905y+ibvOmowIZh6cwbYVK2vpRXezd2UJJll +N1NflSirrJ9RQkTechMYqOmekkqoizc9YIjF2Psg0hyMkq8h1Rxq8hXaESGHrQDy +W2a12lABdUET5TtpmBLV8YBZLYDRrdTC4/amRCno7IoJRUellq1uX0VqEM3ZAHN6 +C/+t0nXPAgMBAAECggEAF+2ZK4lZdsq4AQDVhqUuh4v+NSW/T0NHCWxto6OLWPzJ +N09BV5LKIvdD9yaM1HCj9XCgXOooyfYuciuhARo20f+H+VWNY+c+/8GWiSFsTCJG +4+Oao7NwVSWqljp07Ou2Hamo9AjxzGhe6znmlmg62CiW63f45MWQkqksHA0yb5jg +/onJ2//I+OI+aTKNfjt1G6h2x7oxeGTU1jJ0Hb2xSh+Mpqx9NDfb/KZyOndhSG5N +xRVosQ6uV+9mqHxTTwTZurTG31uhZzarkMuqxhcHS94ub7berEc/OlqvbyMKNZ3A +lzuvq0NBZhEUhAVgORAIS17r/q2BvyG4u5LFbG2p0QKBgQDeyyOl+A7xc4lPE2OL +Z3KHJPP4RuUnHnWFC+bNdr5Ag8K7jcjZIcasyUom9rOR0Fpuw9wmXpp3+6fyp9bJ +y6Bi5VioR0ZFP5X+nXxIN3yvgypu6AZvkhHrEFer+heGHxPlbwNKCKMbPzDZPBTZ +vlC7g7xUUcpNmGhrOKr3Qq5FlwKBgQDdgCmRvsHUyzicn8TI3IJBAOcaQG0Yr/R2 +FzBqNfHHx7fUZlJfKJsnu9R9VRZmBi4B7MA2xcvz4QrdZWEtY8uoYp8TAGILfW1u +CP4ZHrzfDo/67Uzk2uTMTd0+JOqSm/HiVNguRPvC8EWBoFls+h129GKThMvKR1hP +1oarfAGIiQKBgQCIMAq5gHm59JMhqEt4QqMKo3cS9FtNX1wdGRpbzFMd4q0dstzs +ha4Jnv3Z9YHtBzzQap9fQQMRht6yARDVx8hhy6o3K2J0IBtTSfdXubtZGkfNBb4x +Y0vaseG1uam5jbO+0u5iygbSN/1nPUfNln2JMkzkCh8s8ZYavMgdX0BiPwKBgChR +QL/Hog5yoy5XIoGRKaBdYrNzkKgStwObuvNKOGUt5DckHNA3Wu6DkOzzRO1zKIKv +LlmJ7VLJ3qln36VcaeCPevcBddczkGyb9GxsHOLZCroY4YsykLzjW2cJXy0qd3/E +A8mAQvc7ttsebciZSi2x1BOX82QxUlDN8ptaKglJAoGBAMnLN1TQB0xtWYDPGcGV +2IvgX7OTRRlMVrTvIOvP5Julux9z1r0x0cesl/jaXupsBUlLLicPyBMSBJrXlr24 +mrgkodk4TdqO1VtBCZBqak97DHVezstMrbpCGlUD5jBnsHVRLERvS09QlGhqMeNL +jpNQbWH9VhutzbvpYquKrhvK +-----END PRIVATE KEY----- """, # 2 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQyMDZaFw0wOTA3MjUyMjQyMDZaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxnH+pbOS -qlJlsHpKUQtV0oN1Mv+ESG+yUDxStFFGjkJv/UIRzpxqFqY/6nJ3D03kZsDdcXyi -CfV9hPYQaVNMn6z+puPmIagfBQ0aOyuI+nUhCttZIYD9071BjW5bCMX5NZWL/CZm -E0HdAZ77H6UrRckJ7VR8wAFpihBxD5WliZcCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQAwXqY1Sjvp9JSTHKklu7s0T6YmH/BKSXrHpS2xO69svK+ze5/+5td3jPn4Qe50 -xwRNZSFmSLuJLfCO32QJSJTB7Vs5D3dNTZ2i8umsaodm97t8hit7L75nXRGHKH// -xDVWAFB9sSgCQyPMRkL4wB4YSfRhoSKVwMvaz+XRZDUU0A== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNDAzM1oXDTIxMDEwMTAxNDAzM1owFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAypqi +YTni3s60Uo8vgGcFvjWWkB5CD9Fx9pW/2KcxRJ/u137Y+BG8qWMA4lgII3ZIuvo4 +6rLDiXnAnDZqUtrvZ90O/gH6RyQqX3AI4EwPvCnRIIe0okRcxnxYBL/LfBY54xuv +46JRYZP4c9IImqQH9QVo2/egtEzcpbmT/mfhpf6NGQWC3Xps2BqDT2SV/DrX/wPA +8P1atE1AxNp8ENxK/cjFAteEyDZOsDSa757ZHKAdM7L8rZ1Fd2xAA1Dq7IyYpTNE +IX72xytWxllcNvSUPLT+oicsSZBadc/p3moc3tR/rNdgrHKybedadru/f9Gwpa+v +0sllZlEcVPSYddAzWwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmk60Nj5FPvemx +DSSQjJPyJoIDpTxQ4luSzIq4hPwlUXw7dqrvHyCWgn2YVe9xZsGrT/+n376ecmgu +sw4s4qVhR9bzKkTMewjC2wUooTA5v9HYsNWZy3Ah7hHPbDHlMADYobjB5/XolNUP +bCM9xALEdM9DxpC4vjUZexlRKmjww9QKE22jIM+bqsK0zqDSq+zHpfHNGGcS3vva +OvI6FPc1fAr3pZpVzevMSN2zufIJwjL4FT5/uzwOCaSCwgR1ztD5CSbQLTLlwIsX +S7h2WF9078XumeRjKejdjEjyH4abKRq8+5LVLcjKEpg7OvktuRpPoGPCEToaAzuv +h+RSQwwY -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDGcf6ls5KqUmWwekpRC1XSg3Uy/4RIb7JQPFK0UUaOQm/9QhHO -nGoWpj/qcncPTeRmwN1xfKIJ9X2E9hBpU0yfrP6m4+YhqB8FDRo7K4j6dSEK21kh -gP3TvUGNblsIxfk1lYv8JmYTQd0BnvsfpStFyQntVHzAAWmKEHEPlaWJlwIDAQAB -AoGAdHNMlXwtItm7ZrY8ihZ2xFP0IHsk60TwhHkBp2LSXoTKJvnwbSgIcUYZ18BX -8Zkp4MpoqEIU7HcssyuaMdR572huV2w0D/2gYJQLQ5JapaR3hMox3YG4wjXasN1U -1iZt7JkhKlOy+ElL5T9mKTE1jDsX2RAv4WALzMpYFo7vs4ECQQDxqrPaqRQ5uYS/ -ejmIk05nM3Q1zmoLtMDrfRqrjBhaf/W3hqGihiqN2kL3PIIYcxSRWiyNlYXjElsR -2sllBTe3AkEA0jcMHVThwKt1+Ce5VcE7N6hFfbsgISTjfJ+Q3K2NkvJkmtE8ZRX5 -XprssnPN8owkfF5yuKbcSZL3uvaaSGN9IQJAfTVnN9wwOXQwHhDSbDt9/KRBCnum -n+gHqDrKLaVJHOJ9SZf8eLswoww5c+UqtkYxmtlwie61Tp+9BXQosilQ4wJBAIZ1 -XVNZmriBM4jR59L5MOZtxF0ilu98R+HLsn3kqLyIPF9mXCoQPxwLHkEan213xFKk -mt6PJDIPRlOZLqAEuuECQFQMCrn0VUwPg8E40pxMwgMETvVflPs/oZK1Iu+b7+WY -vBptAyhMu31fHQFnJpiUOyHqSZnOZyEn1Qu2lszNvUg= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDKmqJhOeLezrRS +jy+AZwW+NZaQHkIP0XH2lb/YpzFEn+7Xftj4EbypYwDiWAgjdki6+jjqssOJecCc +NmpS2u9n3Q7+AfpHJCpfcAjgTA+8KdEgh7SiRFzGfFgEv8t8FjnjG6/jolFhk/hz +0giapAf1BWjb96C0TNyluZP+Z+Gl/o0ZBYLdemzYGoNPZJX8Otf/A8Dw/Vq0TUDE +2nwQ3Er9yMUC14TINk6wNJrvntkcoB0zsvytnUV3bEADUOrsjJilM0QhfvbHK1bG +WVw29JQ8tP6iJyxJkFp1z+neahze1H+s12CscrJt51p2u79/0bClr6/SyWVmURxU +9Jh10DNbAgMBAAECggEBALv7Q+Rf+C7wrQDZF6LUc9CrGfq4CGVy2IGJKgqT/jOF +DO9nI1rv4hNr55sbQNneWtcZaYvht2mrzNlj57zepDjDM7DcFuLBHIuWgLXT/NmC +FyZOo3vXYBlNr8EgT2XfnXAp9UWJCmc2CtUzsIYC4dsmXMeTd8kyc5tUl4r5ybTf +1g+RTck/IGgqdfzpuTsNl79FW2rP9z111Py6dbqgQzhuSAune9dnLFvZst8dyL8j +FStETMxBM6jrCF1UcKXzG7trDHiCdzJ8WUhx6opN/8OasQGndwpXto6FZuBy/AVP +4kVQNpUXImYcLEpva0MqGRHg+YN+c84C71CMchnF4aECgYEA7J2go4CkCcZNKCy5 +R5XVCqNFYRHjekR+UwH8cnCa7pMKKfP+lTCiBrO2q8zwWwknRMyuycS5g/xbSpg1 +L6hi92CV1YQy1/JhlQedekjejNTTuLOPKf78AFNSfc7axDnes2v4Bvcdp9gsbUIO +10cXh0tOSLE7P9y+yC86KQkFAPECgYEA2zO0M2nvbPHv2jjtymY3pflYm0HzhM/T +kPtue3GxOgbEPsHffBGssShBTE3yCOX3aAONXJucMrSAPL9iwUfgfGx6ADdkwBsA +OjDlkxvTbP/9trE6/lsSPtGpWRdJNHqXN4Hx7gXJizRwG7Ym+oHvIIh53aIjdFoE +HLQLpxObuQsCgYAuMQ99G83qQpYpc6GwAeYXL4yJyK453kky9z5LMQRt8rKXQhS/ +F0FqQYc1vsplW0IZQkQVC5yT0Z4Yz+ICLcM0O9zEVAyA78ZxC42Io9UedSXn9tXK +Awc7IQkHmmxGxm1dZYSEB5X4gFEb+zted3h2ZxMfScohS3zLI70c6a/aYQKBgQCU +phRuxUkrTUpFZ1PCbN0R/ezbpLbaewFTEV7T8b6oxgvxLxI6FdZRcSYO89DNvf2w +GLCVe6VKMWPBTlxPDEostndpjCcTq3vU+nHE+BrBkTvh14BVGzddSFsaYpMvNm8z +ojiJHH2XnCDmefkm6lRacJKL/Tcj4SNmv6YjUEXLDwKBgF8WV9lzez3d/X5dphLy +2S7osRegH99iFanw0v5VK2HqDcYO9A7AD31D9nwX46QVYfgEwa6cHtVCZbpLeJpw +qXnYXe/hUU3yn5ipdNJ0Dm/ZhJPDD8TeqhnRRhxbZmsXs8EzfwB2tcUbASvjb3qA +vAaPlOSU1wXqhAsG9aVs8gtL +-----END PRIVATE KEY----- """, # 3 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQyMDZaFw0wOTA3MjUyMjQyMDZaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAnjiOwipn -jigDuNMfNG/tBJhPwYUHhSbQdvrTubhsxw1oOq5XpNqUwRtC8hktOKM3hghyqExP -62EOi0aJBkRhtwtPSLBCINptArZLfkog/nTIqVv4eLEzJ19nTi/llHHWKcgA6XTI -sU/snUhGlySA3RpETvXqIJTauQRZz0kToSUCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQCQ+u/CsX5WC5m0cLrpyIS6qZa62lrB3mj9H1aIQhisT5kRsMz3FJ1aOaS8zPRz -w0jhyRmamCcSsWf5WK539iOtsXbKMdAyjNtkQO3g+fnsLgmznAjjst24jfr+XU59 -0amiy1U6TY93gtEBZHtiLldPdUMsTuFbBlqbcMBQ50x9rA== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNDAzNFoXDTIxMDEwMTAxNDAzNFowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzUqQ +M08E7F2ZE99bFHvpsR6LmgIJOOoGMXacTcEUhRF63E6+730FjxER2a30synv9GGS +3G9FstUmfhyimufkbTumri8Novw5CWZQLiE1rmMBI5nPcR2wAzy9z2odR6bfAwms +yyc3IPYg1BEDBPZl0LCQrQRRU/rVOrbCf7IMq+ATazmBg01gXMzq2M953ieorkQX +MsHVR/kyW0Q0yzhYF1OtIqbXxrdiZ+laTLWNqivj/FdegiWPCf8OcqpcpbgEjlDW +gBcC/vre+0E+16nfUV8xHL5jseJMJqfT508OtHxAzp+2D7b54NvYNIvbOAP+F9gj +aXy5mOvjXclK+hNmDwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAjZzTFKG7uoXxm +BPHfQvsKHIB/Cx9zMKj6pLwJzCPHQBzKOMoUen09oq+fb77RM7WvdX0pvFgEXaJW +q/ImooRMo+paf8GOZAuPwdafb2/OGdHZGZ2Cbo/ICGo1wGDCdMvbxTxrDNq1Yae+ +m+2epN2pXAO1rlc7ktRkojM/qi3zXtbLjTs3IoPDXWhYPHdI1ThkneRmvxpzB1rW +2SBqj2snvyI+/3k3RHmldcdOrTlgWQ9hq05jWR8IVtRUFFVn9A+yQC3gnnLIUhwP +HJWwTIPuYW25TuxFxYZXIbnAiluZL0UIjd3IAwxaafvB6uhI7v0K789DKj2vRUkY +E8ptxZH4 -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCeOI7CKmeOKAO40x80b+0EmE/BhQeFJtB2+tO5uGzHDWg6rlek -2pTBG0LyGS04ozeGCHKoTE/rYQ6LRokGRGG3C09IsEIg2m0Ctkt+SiD+dMipW/h4 -sTMnX2dOL+WUcdYpyADpdMixT+ydSEaXJIDdGkRO9eoglNq5BFnPSROhJQIDAQAB -AoGAAPrst3s3xQOucjismtCOsVaYN+SxFTwWUoZfRWlFEz6cBLELzfOktEWM9p79 -TrqEH4px22UNobGqO2amdql5yXwEFVhYQkRB8uDA8uVaqpL8NLWTGPRXxZ2DSU+n -7/FLf/TWT3ti/ZtXaPVRj6E2/Mq9AVEVOjUYzkNjM02OxcECQQDKEqmPbdZq2URU -7RbUxkq5aTp8nzAgbpUsgBGQ9PDAymhj60BDEP0q28Ssa7tU70pRnQ3AZs9txgmL -kK2g97FNAkEAyHH9cIb6qXOAJPIr/xamFGr5uuYw9TJPz/hfVkVimW/aZnBB+e6Q -oALJBDKJWeYPzdNbouJYg8MeU0qWdZ5DOQJADUk+1sxc/bd9U6wnBSRog1pU2x7I -VkmPC1b8ULCaJ8LnLDKqjf5O9wNuIfwPXB1DoKwX3F+mIcyUkhWYJO5EPQJAUj5D -KMqZSrGzYHVlC/M1Daee88rDR7fu+3wDUhiCDkbQq7tftrbl7GF4LRq3NIWq8l7I -eJq6isWiSbaO6Y+YMQJBAJFBpVhlY5Px2BX5+Hsfq6dSP3sVVc0eHkdsoZFFxq37 -fksL/q2vlPczvBihgcxt+UzW/UrNkelOuX3i57PDvFs= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDNSpAzTwTsXZkT +31sUe+mxHouaAgk46gYxdpxNwRSFEXrcTr7vfQWPERHZrfSzKe/0YZLcb0Wy1SZ+ +HKKa5+RtO6auLw2i/DkJZlAuITWuYwEjmc9xHbADPL3Pah1Hpt8DCazLJzcg9iDU +EQME9mXQsJCtBFFT+tU6tsJ/sgyr4BNrOYGDTWBczOrYz3neJ6iuRBcywdVH+TJb +RDTLOFgXU60iptfGt2Jn6VpMtY2qK+P8V16CJY8J/w5yqlyluASOUNaAFwL++t77 +QT7Xqd9RXzEcvmOx4kwmp9PnTw60fEDOn7YPtvng29g0i9s4A/4X2CNpfLmY6+Nd +yUr6E2YPAgMBAAECggEBAIiL6uQl0AmDrBj6vHMghGzp+0MBza6MgngOA6L4JTTp +ToYQ3pEe4D6rxOq7+QHeiBtNd0ilvn9XpVXGqCVOzrIVNiWvaGubRjjJU9WLA1Ct +y4kpekAr1fIhScMXOsh45ub3XXZ27AVBkM5dTlvTpB8uAd0C/TFVqtR10WLsQ99h +Zm9Jczgs/6InYTssnAaqdeCLAf1LbmO4zwFsJfJOeSGGT6WBwlpHwMAgPhg8OLEu +kVWG7BEJ0hxcODk/es/vce9SN7BSyIzNY+qHcGtsrx/o0eO2Av/Z7ltV4Sz6UN1K +0y0OTiDyT/l62U2OugSN3wQ4xPTwlrWl7ZUHJmvpEaECgYEA+w2JoB2i1OV2JTPl +Y0TKSKcZYdwn7Nwh4fxMAJNJ8UbpPqrZEo37nxqlWNJrY/jKX3wHVk4ESSTaxXgF +UY7yKT0gRuD9+vE0gCbUmJQJTwbceNJUu4XrJ6SBtf72WgmphL+MtyKdwV8XltVl +Yp0hkswGmxl+5+Js6Crh7WznPl8CgYEA0VYtKs2YaSmT1zraY6Fv3AIQZq012vdA +7nVxmQ6jKDdc401OWARmiv0PrZaVNiEJ1YV8KxaPrKTfwhWqxNegmEBgA1FZ66NN +SAm8P9OCbt8alEaVkcATveXTeOCvfpZUO3sqZdDOiYLiLCsokHblkcenK85n0yT6 +CzhTbvzDllECgYEAu9mfVy2Vv5OK2b+BLsw0SDSwa2cegL8eo0fzXqLXOzCCKqAQ +GTAgTSbU/idEr+NjGhtmKg/qaQioogVyhVpenLjeQ+rqYDDHxfRIM3rhlD5gDg/j +0wUbtegEHrgOgcSlEW16zzWZsS2EKxq16BoHGx6K+tcS/FOShg5ASzWnuiUCgYEA +sMz+0tLX8aG7CqHbRyBW8FMR9RY/kRMY1Q1+Bw40wMeZfSSSkYYN8T9wWWT/2rqm +qp7V0zJ34BFUJoDUPPH84fok3Uh9EKZYpAoM4z9JP0jREwBWXMYEJnOQWtwxfFGN +DLumgF2Nwtg3G6TL2s+AbtJYH4hxagQl5woIdYmnyzECgYEAsLASpou16A3uXG5J ++5ZgF2appS9Yfrqfh6TKywMsGG/JuiH3djdYhbJFIRGeHIIDb4XEXOHrg/SFflas +If0IjFRh9WCvQxnoRha3/pKRSc3OEka1MR/ZREK/d/LQEPmsRJVzY6ABKqmPAMDD +5CnG6Hz/rP87BiEKd1+3PGp8GCw= +-----END PRIVATE KEY----- """, # 4 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQyMDZaFw0wOTA3MjUyMjQyMDZaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsCQuudDF -zgmY5tDpT0TkUo8fpJ5JcvgCkLFpSDD8REpXhLFkHWhTmTj3CAxfv4lA3sQzHZxe -4S9YCb5c/VTbFEdgwc/wlxMmJiz2jYghdmWPBb8pBEk31YihIhC+u4kex6gJBH5y -ixiZ3PPRRMaOBBo+ZfM50XIyWbFOOM/7FwcCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQB4cFURaiiUx6n8eS4j4Vxrii5PtsaNEI4acANFSYknGd0xTP4vnmoivNmo5fWE -Q4hYtGezNu4a9MnNhcQmI20KzXmvhLJtkwWCgGOVJtMem8hDWXSALV1Ih8hmVkGS -CI1elfr9eyguunGp9eMMQfKhWH52WHFA0NYa0Kpv5BY33A== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNDAzNFoXDTIxMDEwMTAxNDAzNFowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0sap +75YbbkEL85LFava3FrO1jpgVteQ4NGxxy1Nu9w2hPfMMeCPWjB8UfAwFk+LVPyvW +LAXd1zWL5rGpQ2ytIVQlTraR5EnALA1sMcQYbFz1ISPTYB031bEN/Ch8JWYwCG5A +X2H4D6BC7NgT6YyWDt8vxQnqAisPHQ/OK4ABD15CwkTyPimek2/ufYN2dapg1xhG +IUD96gqetJv9bu0r869s688kADIComsYG+8KKfFN67S3rSHMIpZPuGTtoHGnVO89 +XBm0vNe0UxQkJEGJzZPn0tdec0LTC4GNtTaz5JuCjx/VsJBqrnTnHHjx0wFz8pff +afCimRwA+LCopxPE1QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBOkAnpBb3nY+dG +mKCjiLqSsuEPqpNiBYR+ue/8aVDnOKLKqAyQuyRZttQ7bPpKHaw7pwyCZH8iHnt6 +pMCLCftNSlV2Fa8msRmuf5AiGjUvR1M8VtHWNYE8pedWrJqUgBhF/405B99yd8CT +kQJXKF18LObj7YKNsWRoMkVgqlQzWDMEqbfmy9MhuLx2EZPsTB1L0BHNGGDVBd9o +cpPLUixcc12u+RPMKq8x3KgwsnUf5vX/pCnoGcCy4JahWdDgcZlf0hUKGT7PUem5 +CWW8SMeqSWQX9XpE5Qlm1+W/QXdDXLbbHqDtvBeUy3iFQe3C9RSkp0qdutxkAlFk +f5QHXfJ7 -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQCwJC650MXOCZjm0OlPRORSjx+knkly+AKQsWlIMPxESleEsWQd -aFOZOPcIDF+/iUDexDMdnF7hL1gJvlz9VNsUR2DBz/CXEyYmLPaNiCF2ZY8FvykE -STfViKEiEL67iR7HqAkEfnKLGJnc89FExo4EGj5l8znRcjJZsU44z/sXBwIDAQAB -AoGABA7xXKqoxBSIh1js5zypHhXaHsre2l1Igdj0mgs25MPpvE7yBZNvyan8Vx0h -36Hj8r4Gh3og3YNfvem67sNTwNwONY0ep+Xho/3vG0jFATGduSXdcT04DusgZNqg -UJqW75cqxrD6o/nya5wUoN9NL5pcd5AgVMdOYvJGbrwQuaECQQDiCs/5dsUkUkeC -Tlur1wh0wJpW4Y2ctO3ncRdnAoAA9y8dELHXMqwKE4HtlyzHY7Bxds/BDh373EVK -rsdl+v9JAkEAx3xTmsOQvWa1tf/O30sdItVpGogKDvYqkLCNthUzPaL85BWB03E2 -xunHcVVlqAOE5tFuw0/UEyEkOaGlNTJTzwJAPIVel9FoCUiKYuYt/z1swy3KZRaw -/tMmm4AZHvh5Y0jLcYHFy/OCQpRkhkOitqQHWunPyEXKW2PnnY5cTv68GQJAHG7H -B88KCUTjb25nkQIGxBlA4swzCtDhXkAb4rEA3a8mdmfuWjHPyeg2ShwO4jSmM7P0 -Iph1NMjLff9hKcTjlwJARpItOFkYEdtSODC7FMm7KRKQnNB27gFAizsOYWD4D2b7 -w1FTEZ/kSA9wSNhyNGt7dgUo6zFhm2u973HBCUb3dg== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSxqnvlhtuQQvz +ksVq9rcWs7WOmBW15Dg0bHHLU273DaE98wx4I9aMHxR8DAWT4tU/K9YsBd3XNYvm +salDbK0hVCVOtpHkScAsDWwxxBhsXPUhI9NgHTfVsQ38KHwlZjAIbkBfYfgPoELs +2BPpjJYO3y/FCeoCKw8dD84rgAEPXkLCRPI+KZ6Tb+59g3Z1qmDXGEYhQP3qCp60 +m/1u7Svzr2zrzyQAMgKiaxgb7wop8U3rtLetIcwilk+4ZO2gcadU7z1cGbS817RT +FCQkQYnNk+fS115zQtMLgY21NrPkm4KPH9WwkGqudOccePHTAXPyl99p8KKZHAD4 +sKinE8TVAgMBAAECggEALU5EotoqJUXYEtAenUJQ0pFoWjE4oXNf3Wzd/O1/MZ19 +ZjqDGKPjbxUTKyLOZB5i5gQ/MhFEwQiifMD9eB+5CyvyJPw7Wc28f/uWoQ/cjBZj +Hm979PHy2X0IW4Y8QTG462b/cUE2t+0j1ZMQnKf6bVHuC7V41mR5CC8oitMl5y5g +34yJmWXlIA0ep/WotLMqvil6DnSM/2V8Ch4SxjnzPpjbe4Kj+woucGNr4UKstZER +8iuHTsR64LjoGktRnnMwZxGZQI7EC428zsliInuWMdXe//w2chLdkirqpSrIQwSZ +3jNWStqBXGYaRg5Z1ilBvHtXxkzDzbAlzRBzqfEwwQKBgQDqYdMRrzHJaXWLdsyU +6jAuNX9tLh7PcicjP93SbPujS6mWcNb+D/au+VhWD+dZQDPRZttXck7wvKY1lw1V +MK0TYI7ydf8h3DFx3Mi6ZD4JVSU1MH233C3fv/FHenDoOvMXXRjUZxaRmuzFJvzt +6QlKIfSvwT+1wrOACNfteXfZUQKBgQDmN3Uuk01qvsETPwtWBp5RNcYhS/zGEQ7o +Q4K+teU453r1v8BGsQrCqulIZ3clMkDru2UroeKn1pzyVAS2AgajgXzfXh3VeZh1 +vHTLP91BBYZTTWggalEN4aAkf9bxX/hA+9Bw/dzZcQW2aNV7WrYuCSvp3SDCMina +anQq/PaSRQKBgHjw23HfnegZI89AENaydQQTFNqolrtiYvGcbgC7vakITMzVEwrr +/9VP0pYuBKmYKGTgF0RrNnKgVX+HnxibUmOSSpCv9GNrdJQVYfpT6XL1XYqxp91s +nrs7FuxUMNiUOoWOw1Yuj4W4lH4y3QaCXgnDtbfPFunaOrdRWOIv8HjRAoGAV3NT +mSitbNIfR69YIAqNky3JIJbb42VRc1tJzCYOd+o+pCF96ZyRCNehnDZpZQDM9n8N +9GAfWEBHCCpwS69DVFL422TGEnSJPJglCZwt8OgnWXd7CW05cvt1OMgzHyekhxLg +4Dse7J5pXBxAlAYmVCB5xPGR4xLpISX1EOtcwr0CgYEA5rA2IUfjZYb4mvFHMKyM +xWZuV9mnl3kg0ULttPeOl3ppwjgRbWpyNgOXl8nVMYzxwT/A+xCPA18P0EcgNAWc +frJqQYg3NMf+f0K1wSaswUSLEVrQOj25OZJNpb21JEiNfEd5DinVVj4BtVc6KSpS +kvjbn2WhEUatc3lPL3V0Fkw= +-----END PRIVATE KEY----- """, # 5 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQ3NThaFw0wOTA3MjUyMjQ3NThaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvhTRj1dA -NOfse/UBeTfMekZKxZHsNPr+qBYaveWAHDded/BMyMgaMV2n6HQdiDaRjJkzjHCF -3xBtpIJeEGUqfrF0ob8BIZXy3qk68eX/0CVUbgmjSBN44ahlo63NshyXmZtEAkRV -VE/+cRKw3N2wtuTed5xwfNcL6dg4KTOEYEkCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQCN+CLuVwLeWjSdVbdizYyrOVckqtwiIHG9BbGMlcIdm0qpvD7V7/sN2csk5LaT -BNiHi1t5628/4UHqqodYmFw8ri8ItFwB+MmTJi11CX6dIP9OUhS0qO8Z/BKtot7H -j04oNwl+WqZZfHIYwTIEL0HBn60nOvCQPDtnWG2BhpUxMA== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNTExM1oXDTIxMDEwMTAxNTExM1owFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1c5y +S9IZHF9MIuwdafzhMkgP37I3RVpHEbpnPwnLFqSWelS5m2eDkwWd5SkfGjrmQ5q0 +PEpqLlh3zHGw9yQjnHS3CCS1PwQ1kmwvpIK3HM5y8GM7ry1zkam8ZR4iX6Y7VG9g +9mhiVVFoVhe1gHeiC/3Mp6XeNuEiD0buM+8qZx9B21I+iwzy4wva7Gw0fJeq9G1c +lq2rhpD1LlIEodimWOi7lOEkNmUiO1SvpdrGdxUDpTgbdg6r5pCGjOXLd74tAQHP +P/LuqRNJDXtwvHtLIVQnW6wjjy4oiWZ8DXOdc9SkepwQLIF5Wh8O7MzF5hrd6Cvw +SOD3EEsJbyycAob6RwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBDNcbKVUyGOAVm +k3iVuzkkkymlTAMm/gsIs6loLJrkSqNg160FdVKJoZFjQtqoqLgLrntdCJ377nZ9 +1i+yzbZsA4DA7nxj0IEdnd7rRYgGLspGqWeKSTROATeT4faLTXenecm0v2Rpxqc7 +dSyeZJXOd2OoUu+Q64hzXCDXC6LNM+xZufxV9qv+8d+CipV6idSQZaUWSVuqFCwD +PT0R4eWfkMMaM8QqtNot/hVCEaKT+9rG0mbpRe/b/qBy5SR0u+XgGEEIV+33L59T +FXY+DpI1Dpt/bJFoUrfj6XohxdTdqYVCn1F8in98TsRcFHyH1xlkS3Y0RIiznc1C +BwAoGZ4B -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQC+FNGPV0A05+x79QF5N8x6RkrFkew0+v6oFhq95YAcN1538EzI -yBoxXafodB2INpGMmTOMcIXfEG2kgl4QZSp+sXShvwEhlfLeqTrx5f/QJVRuCaNI -E3jhqGWjrc2yHJeZm0QCRFVUT/5xErDc3bC25N53nHB81wvp2DgpM4RgSQIDAQAB -AoGALl2BqIdN4Bnac3oV++2CcSkIQB0SEvJOf820hDGhCEDxSCxTbn5w9S21MVxx -f7Jf2n3cNxuTbA/jzscGDtW+gXCs+WAbAr5aOqHLUPGEobhKQrQT2hrxQHyv3UFp -0tIl9eXFknOyVAaUJ3athK5tyjSiCZQQHLGzeLaDSKVAPqECQQD1GK7DkTcLaSvw -hoTJ3dBK3JoKT2HHLitfEE0QV58mkqFMjofpe+nyeKWvEb/oB4WBp/cfTvtf7DJK -zl1OSf11AkEAxomWmJeub0xpqksCmnVI1Jt1mvmcE4xpIcXq8sxzLHRc2QOv0kTw -IcFl4QcN6EQBmE+8kl7Tx8SPAVKfJMoZBQJAGsUFYYrczjxAdlba7glyFJsfn/yn -m0+poQpwwFYxpc7iGzB+G7xTAw62WfbAVSFtLYog7aR8xC9SFuWPP1vJeQJBAILo -xBj3ovgWTXIRJbVM8mnl28UFI0msgsHXK9VOw/6i93nMuYkPFbtcN14KdbwZ42dX -5EIrLr+BNr4riW4LqDUCQQCbsEEpTmj3upKUOONPt+6CH/OOMjazUzYHZ/3ORHGp -Q3Wt+I4IrR/OsiACSIQAhS4kBfk/LGggnj56DrWt+oBl ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVznJL0hkcX0wi +7B1p/OEySA/fsjdFWkcRumc/CcsWpJZ6VLmbZ4OTBZ3lKR8aOuZDmrQ8SmouWHfM +cbD3JCOcdLcIJLU/BDWSbC+kgrccznLwYzuvLXORqbxlHiJfpjtUb2D2aGJVUWhW +F7WAd6IL/cynpd424SIPRu4z7ypnH0HbUj6LDPLjC9rsbDR8l6r0bVyWrauGkPUu +UgSh2KZY6LuU4SQ2ZSI7VK+l2sZ3FQOlOBt2DqvmkIaM5ct3vi0BAc8/8u6pE0kN +e3C8e0shVCdbrCOPLiiJZnwNc51z1KR6nBAsgXlaHw7szMXmGt3oK/BI4PcQSwlv +LJwChvpHAgMBAAECggEBAK0KLeUBgIM++Y7WDCRInzYjrn08bpE5tIU7mO4jDfQg +dw1A3wtQZuOpyxW6B0siWlRis/aLv44M2cBkT3ZmEFBDAhOcKfh7fqQn3RNHG847 +pDi8B4UKwxskBa7NCcLh9eirUA19hABLJ6dt/t6fdE5CNc2FZ+iAoyE8JfNwYKAd +6Fa3HqUBPNWt8ryj4ftgpMNBdfmLugEM4N20SXJA28hOq2lUcwNKQQ1xQrovl0ig +iMbMWytV4gUPKC9Wra66OYIkk/K8teiUNIYA4JwAUVTs1NEWoyfwUTz1onutCkMl +5vY7JAqRoDWoSUX6FI+IHUdyqPAMdOMhC37gjrxoo2ECgYEA7trDMu6xsOwEckDh +iz148kejMlnTTuCNetOFBw3njFgxISx0PrDLWmJmnHMxPv9AAjXYb2+UCCm3fj6Q +OB8o4ZJm0n504qbFHcb2aI22U5hZ99ERvqx8WBnJ2RarIBmg06y0ktxq8gFR2qxF +0hWAOcDn1DWQ8QI0XBiFFcJTGtcCgYEA5SdlIXRnVZDKi5YufMAORG9i74dXUi0Y +02UoVxJ+q8VFu+TT8wrC5UQehG3gX+79Cz7hthhDqOSCv6zTyE4Evb6vf9OLgnVe +E5iLF033zCxLSS9MgiZ+jTO+wK3RsapXDtGcSEk2P82Pj5seNf4Ei1GNCRlm1DbX +71wlikprHhECgYABqmLcExAIJM0vIsav2uDiB5/atQelMCmsZpcx4mXv85l8GrxA +x6jTW4ZNpvv77Xm7yjZVKJkGqYvPBI6q5YS6dfPjmeAkyHbtazrCpeJUmOZftQSD +qN5BGwTuT5sn4SXe9ABaWdEhGONCPBtMiLvZK0AymaEGHTbSQZWD/lPoBwKBgGhk +qg2zmd/BNoSgxkzOsbE7jTbR0VX+dXDYhKgmJM7b8AjJFkWCgYcwoTZzV+RcW6rj +2q+6HhizAV2QvmpiIIbQd+Mj3EpybYk/1R2ox1qcUy/j/FbOcpihGiVtCjqF/2Mg +2rGTqMMoQl6JrBmsvyU44adjixTiZz0EHZYCkQoBAoGBAMRdmoR4mgIIWFPgSNDM +ISLJxKvSFPYDLyAepLfo38NzKfPB/XuZrcOoMEWRBnLl6dNN0msuzXnPRcn1gc1t +TG7db+hivAyUoRkIW3dB8pRj9dDUqO9OohjKsJxJaQCyH5vPkQFSLbTIgWrHhU+3 +oSPiK/YngDV1AOmPDH7i62po +-----END PRIVATE KEY----- """, #6 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQ3NThaFw0wOTA3MjUyMjQ3NThaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtKhx6sEA -jn6HWc6T2klwlPn0quyHtATIw8V3ezP46v6g2rRS7dTywo4GTP4vX58l+sC9z9Je -qhQ1rWSwMK4FmnDMZCu7AVO7oMIXpXdSz7l0bgCnNjvbpkA2pOfbB1Z8oj8iebff -J33ID5DdkmCzqYVtKpII1o/5z7Jo292JYy8CAwEAATANBgkqhkiG9w0BAQQFAAOB -gQA0PYMA07wo9kEH4fv9TCfo+zz42Px6lUxrQBPxBvDiGYhk2kME/wX0IcoZPKTV -WyBGmDAYWvFaHWbrbbTOfzlLWfYrDD913hCi9cO8iF8oBqRjIlkKcxAoe7vVg5Az -ydVcrY+zqULJovWwyNmH1QNIQfMat0rj7fylwjiS1y/YsA== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAojGu +fQaTVT9DJWJ/zogGfrryEJXYVy9c441O5MrLlRx7nCIWIUs2NEhHDJdqJjYOTdmk +K98VhdMpDPZwxjgvvZrh43lStBRIW3zZxv747rSl2VtpSqD/6UNWJe5u4SR7oga4 +JfITOKHg/+ASxnOxp/iu6oT6jBL6T7KSPh6Rf2+it2rsjhktRreFDJ2hyroNq1w4 +ZVNCcNPgUIyos8u9RQKAWRNchFh0p0FCS9xNrn3e+yHnt+p6mOOF2gMzfXT/M2hq +KQNmc5D3yNoH2smWoz7F3XsRjIB1Ie4VWoRRaGEy7RwcwiDfcaemD0rQug6iqH7N +oomF6f3R4DyvVVLUkQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB/8SX6qyKsOyex +v3wubgN3FPyU9PqMfEzrFM6X5sax0VMVbSnekZrrXpdnXYV+3FBu2GLLQc900ojj +vKD+409JIriTcwdFGdLrQPTCRWkEOae8TlXpTxuNqJfCPVNxFN0znoat1bSRsX1U +K0mfEETQ3ARwlTkrF9CM+jkU3k/pnc9MoCLif8P7OAF38AmIbuTUG6Gpzy8RytJn +m5AiA3sds5R0rpGUu8mFeBpT6jIA1QF2g+QNHKOQcfJdCdfqTjKw5y34hjFqbWG9 +RxWGeGNZkhC/jADCt+m+R6+hlyboLuIcVp8NJw6CGbr1+k136z/Dj+Fdhm6FzF7B +qULeRQJ+ -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQC0qHHqwQCOfodZzpPaSXCU+fSq7Ie0BMjDxXd7M/jq/qDatFLt -1PLCjgZM/i9fnyX6wL3P0l6qFDWtZLAwrgWacMxkK7sBU7ugwheld1LPuXRuAKc2 -O9umQDak59sHVnyiPyJ5t98nfcgPkN2SYLOphW0qkgjWj/nPsmjb3YljLwIDAQAB -AoGAU4CYRv22mCZ7wVLunDLdyr5ODMMPZnHfqj2XoGbBYz0WdIBs5GlNXAfxeZzz -oKsbDvAPzANcphh5RxAHMDj/dT8rZOez+eJrs1GEV+crl1T9p83iUkAuOJFtgUgf -TtQBL9vHaj7DfvCEXcBPmN/teDFmAAOyUNbtuhTkRa3PbuECQQDwaqZ45Kr0natH -V312dqlf9ms8I6e873pAu+RvA3BAWczk65eGcRjEBxVpTvNEcYKFrV8O5ZYtolrr -VJl97AfdAkEAwF4w4KJ32fLPVoPnrYlgLw86NejMpAkixblm8cn51avPQmwbtahb -BZUuca22IpgDpjeEk5SpEMixKe/UjzxMewJBALy4q2cY8U3F+u6sshLtAPYQZIs3 -3fNE9W2dUKsIQvRwyZMlkLN7UhqHCPq6e+HNTM0MlCMIfAPkf4Rdy4N6ZY0CQCKE -BAMaQ6TwgzFDw5sIjiCDe+9WUPmRxhJyHL1/fvtOs4Z4fVRP290ZklbFU2vLmMQH -LBuKzfb7+4XJyXrV1+cCQBqfPFQQZLr5UgccABYQ2jnWVbJPISJ5h2b0cwXt+pz/ -8ODEYLjqWr9K8dtbgwdpzwbkaGhQYpyvsguMvNPMohs= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCiMa59BpNVP0Ml +Yn/OiAZ+uvIQldhXL1zjjU7kysuVHHucIhYhSzY0SEcMl2omNg5N2aQr3xWF0ykM +9nDGOC+9muHjeVK0FEhbfNnG/vjutKXZW2lKoP/pQ1Yl7m7hJHuiBrgl8hM4oeD/ +4BLGc7Gn+K7qhPqMEvpPspI+HpF/b6K3auyOGS1Gt4UMnaHKug2rXDhlU0Jw0+BQ +jKizy71FAoBZE1yEWHSnQUJL3E2ufd77Iee36nqY44XaAzN9dP8zaGopA2ZzkPfI +2gfayZajPsXdexGMgHUh7hVahFFoYTLtHBzCIN9xp6YPStC6DqKofs2iiYXp/dHg +PK9VUtSRAgMBAAECggEANjn0A3rqUUr4UQxwfIV/3mj0O1VN4kBEhxOcd+PRUsYW +EapXycPSmII9ttj8tU/HUoHcYIqSMI7bn6jZJXxtga/BrALJAsnxMx031k8yvOQK +uvPT7Q6M4NkReVcRHRbMeuxSLuWTRZDhn8qznEPb9rOvD1tsRN6nb3PdbwVbUcZh +2F6JDrTyI/Df6nrYQAWOEe2ay7tzgrNYE4vh+DW7oVmyHRgFYA+DIG5Q+7OVWeW5 +bwYYPKlo4/B0L+GfMKfMVZ+5TvFWAK0YD1e/CW1Gv+i/8dWm4O7UNGg5mTnrIcy1 +g5wkKbyea02/np2B/XBsSWXDl6rTDHL7ay0rH2hjEQKBgQDMKSm3miQTIcL/F2kG +ieapmRtSc7cedP967IwUfjz4+pxPa4LiU47OCGp1bmUTuJAItyQyu/5O3uLpAriD +PTU+oVlhqt+lI6+SJ4SIYw01/iWI3EF2STwXVnohWG1EgzuFM/EqoB+mrodNONfG +UmP58vI9Is8fdugXgpTz4Yq9pQKBgQDLYJoyMVrYTvUn5oWft8ptsWZn6JZXt5Bd +aXh+YhNmtCrSORL3XjcH4yjlcn7X8Op33WQTbPo7QAJ1CumJzAI88BZ/8za638xb +nLueviZApCt0bNMEEdxDffxHFc5TyHE+obMKFfApbCnD0ggO6lrZ8jK9prArLOCp +mRU9SSRffQKBgAjoBszeqZI4F9SfBdLmMyzU5A89wxBOFFMdfKLsOua1sBn627PZ +51Hvpg1HaptoosfujWK1NsvkB0wY9UmsYuU/jrGnDaibnO4oUSzN/WaMlsCYszZg +zYFLIXrQ67tgajlOYcf1Qkw4MujYgPlC4N+njI/EM/rwagGUjcDx5uaNAoGASyqz +EuYG63eTSGH89SEaohw0+yaNmnHv23aF4EAjZ4wjX3tUtTSPJk0g6ly84Nbb8d1T +hZJ7kbaAsf2Mfy91jEw4JKYhjkP05c8x0OP6g12p6efmvdRUEmXX/fXjQjgNEtb0 +sz+UedrOPN+9trWLSo4njsyyw+JcTpKTtQj5dokCgYEAg9Y3msg+GvR5t/rPVlKd +keZkrAp0xBJZgqG7CTPXWS1FjwbAPo7x4ZOwtsgjCuE52lar4j+r2Il+CDYeLfxN +h/Jfn6S9ThUh+B1PMvKMMnJUahg8cVL8uQuBcbAy8HPRK78WO2BTnje44wFAJwTc +0liuYqVxZIRlFLRl8nGqog8= +-----END PRIVATE KEY----- """, #7 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQ3NThaFw0wOTA3MjUyMjQ3NThaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAnBfNHycn -5RnYzDN4EWTk2q1BBxA6ZYtlG1WPkj5iKeaYKzUk58zBL7mNOA0ucq+yTwh9C4IC -EutWPaKBSKY5XI+Rdebh+Efq+urtOLgfJHlfcCraEx7hYN+tqqMVgEgnO/MqIsn1 -I1Fvnp89mSYbQ9tmvhSH4Hm+nbeK6iL2tIsCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQBt9zxfsKWoyyV764rRb6XThuTDMNSDaVofqePEWjudAbDu6tp0pHcrL0XpIrnT -3iPgD47pdlwQNbGJ7xXwZu2QTOq+Lv62E6PCL8FljDVoYqR3WwJFFUigNvBT2Zzu -Pxx7KUfOlm/M4XUSMu31sNJ0kQniBwpkW43YmHVNFb/R7g== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu9oO +cFlNukUcLfFrfkEaUiilcHLmn5OokQbj95CGd2ehQCCVwrkunYLBisthRaancFFb +/yM998B0IUsKTsoLi5DAN3/SkSm6GiQIGO05E4eBPljwJ61QQMxh8+1TwQ9HTun1 +ZE1lhVN1aRmI9VsbyTQLjXh9OFNLSJEKb29mXsgzYwYwNOvo+idzXpy4bMyNoGxY +Y+s2FIKehNHHCv4ravDn8rf6DtDOvyN4d0/QyNws9FpAZMXmLwtBJ9exOqKFW43w +97NxgdNiTFyttrTKTi0b+9v3GVdcEZw5b2RMIKi6ZzPof6/0OlThK6C3xzFK3Bp4 +PMjTfXw5yyRGVBnZZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA4Ms6LqzMu757z +bxISiErRls6fcnq0fpSmiPNHNKM7YwG9KHYwPT6A0UMt30zDwNOXCQBI19caGeeO +MLPWa7Gcqm2XZB2jQwvLRPeFSy9fm6RzJFeyhrh/uFEwUetwYmi/cqeIFDRDBQKn +bOaXkBk0AaSmI5nRYfuqpMMjaKOFIFcoADw4l9wWhv6DmnrqANzIdsvoSXi5m8RL +FcZQDZyHFlHh3P3tLkmQ7ErM2/JDwWWPEEJMlDm/q47FTOQSXZksTI3WRqbbKVv3 +iQlJjpgi9yAuxZwoM3M4975iWH4LCZVMCSqmKCBt1h9wv4LxqX/3kfZhRdy1gG+j +41NOSwJ/ -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQCcF80fJyflGdjMM3gRZOTarUEHEDpli2UbVY+SPmIp5pgrNSTn -zMEvuY04DS5yr7JPCH0LggIS61Y9ooFIpjlcj5F15uH4R+r66u04uB8keV9wKtoT -HuFg362qoxWASCc78yoiyfUjUW+enz2ZJhtD22a+FIfgeb6dt4rqIva0iwIDAQAB -AoGBAIHstcnWd7iUeQYPWUNxLaRvTY8pjNH04yWLZEOgNWkXDVX5mExw++RTmB4t -qpm/cLWkJSEtB7jjthb7ao0j/t2ljqfr6kAbClDv3zByAEDhOu8xB/5ne6Ioo+k2 -dygC+GcVcobhv8qRU+z0fpeXSP8yS1bQQHOaa17bSGsncvHRAkEAzwsn8jBTOqaW -6Iymvr7Aql++LiwEBrqMMRVyBZlkux4hiKa2P7XXEL6/mOPR0aI2LuCqE2COrO7R -0wAFZ54bjwJBAMEAe6cs0zI3p3STHwA3LoSZB81lzLhGUnYBvOq1yoDSlJCOYpld -YM1y3eC0vwiOnEu3GG1bhkW+h6Kx0I/qyUUCQBiH9NqwORxI4rZ4+8S76y4EnA7y -biOx9KxYIyNgslutTUHYpt1TmUDFqQPfclvJQWw6eExFc4Iv5bJ/XSSSyicCQGyY -5PrwEfYTsrm5fpwUcKxTnzxHp6WYjBWybKZ0m/lYhBfCxmAdVrbDh21Exqj99Zv0 -7l26PhdIWfGFtCEGrzECQQCtPyXa3ostSceR7zEKxyn9QBCNXKARfNNTBja6+VRE -qDC6jLqzu/SoOYaqa13QzCsttO2iZk8Ygfy3Yz0n37GE ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC72g5wWU26RRwt +8Wt+QRpSKKVwcuafk6iRBuP3kIZ3Z6FAIJXCuS6dgsGKy2FFpqdwUVv/Iz33wHQh +SwpOyguLkMA3f9KRKboaJAgY7TkTh4E+WPAnrVBAzGHz7VPBD0dO6fVkTWWFU3Vp +GYj1WxvJNAuNeH04U0tIkQpvb2ZeyDNjBjA06+j6J3NenLhszI2gbFhj6zYUgp6E +0ccK/itq8Ofyt/oO0M6/I3h3T9DI3Cz0WkBkxeYvC0En17E6ooVbjfD3s3GB02JM +XK22tMpOLRv72/cZV1wRnDlvZEwgqLpnM+h/r/Q6VOEroLfHMUrcGng8yNN9fDnL +JEZUGdlnAgMBAAECggEALlZdlW0R9U6y4spYf65Dddy84n4VUWu0+wE+HoUyBiYz +6oOfLYdMbmIgp8H/XpT7XINVNBxXXtPEUaoXAtRoAKdWItqO8Gvgki4tKSjrGVwl +j2GU69SepT1FNExoiojgSCEB/RnyXu71WVWJKSyuL/V8nAsKqGgze9T7Q/2wvNQt +SQqLxZlrWF0P8WqaAiSrHV4GnDrdeF+k1KBo2+pSaDNv6cNwOyVG8EII9tqhF8kj +6nD6846ish6OqmlSisaSGopJZL1DCQzszFMxKd2+iBDY7Kn6hVIhRaNnaZUFhpKM +dNh6hBqOycMepAp0sz5pdo+fxpifkoR/cPWgyC3QkQKBgQDixe9VsiZ7u2joxF/9 +JcAExKhqE28OUmIwt6/j+uzYShxN6Oo9FUo3ICtAPCCFsjhvb3Qum7FspmxrqtNy +fzclibZJPO8ey2PzqaiOfiVfgJmNSvoCOdgM4OqFLtRO6eSTzhJeI4VPrPcq/5la +0FuOi1WZs/Au9llqLqGSDH3UAwKBgQDUD/bSJbOk5SvNjFtFm0ClVJr66mJ5e4uN +4VGv8KGFAJ+ExIxujAukfKdwLjS1wEy2RePcshfT8Y9FVh/Q1KzzrQi3Gwmfq1G6 +Dpu2HlJpaZl+9T81x2KS8GP3QNczWMe2nh7Lj+6st+b4F+6FYbVTFnHaae27sXrD +XPX15+uxzQKBgGy+pBWBF4kwBo/QU4NuTdU7hNNRPGkuwl1ASH1Xv6m8aDRII8Nk +6TDkITltW98g5oUxehI7oOpMKCO9SCZYsNY0YpBeQwCOYgDfc6/Y+A0C+x9RO/BD +UsJiPLPfD/pDmNPz9sTj3bKma+RXq29sCOujD0pkiiHLCnerotkJWnGHAoGAAkCJ +JoIv/jhQ1sX+0iZr8VWMr819bjzZppAWBgBQNtFi4E4WD7Z9CSopvQ9AkA2SwvzL +BrT9e8q88sePXvBjRdM4nHk1CPUQ0SEGllCMH4J3ltmT6kZLzbOv3BhcMLdop4/W +U+MbbcomMcxPRCtdeZxraR5m3+9qlliOZCYqYqECgYA5eLdxgyHxCS33QGFHRvXI +TLAHIrr7wK1xwgkmZlLzYSQ8Oqh1UEbgoMt4ulRczP2g7TCfvANw2Sw0H2Q5a6Fj +cnwVcXJ38DLg0GCPMwzE8dK7d8tKtV6kGiKy+KFvoKChPjE6uxhKKmCJaSwtQEPS +vsjX3iiIgUQPsSz8RrNFfQ== +-----END PRIVATE KEY----- """, #8 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQ3NThaFw0wOTA3MjUyMjQ3NThaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4mnLf+x0 -CWKDKP5PLZ87t2ReSDE/J5QoI5VhE0bXaahdhPrQTC2wvOpT+N9nzEpI9ASh/ejV -kYGlc03nNKRL7zyVM1UyGduEwsRssFMqfyJhI1p+VmxDMWNplex7mIAheAdskPj3 -pwi2CP4VIMjOj368AXvXItPzeCfAhYhEVaMCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQAEzmwq5JFI5Z0dX20m9rq7NKgwRyAH3h5aE8bdjO8nEc69qscfDRx79Lws3kK8 -A0LG0DhxKB8cTNu3u+jy81tjcC4pLNQ5IKap9ksmP7RtIHfTA55G8M3fPl2ZgDYQ -ZzsWAZvTNXd/eme0SgOzD10rfntA6ZIgJTWHx3E0RkdwKw== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNTExMloXDTIxMDEwMTAxNTExMlowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DNu +CKhhl6wCbgoCkFemwJh3ATbAjhInHpvQWIFDfSK1USElCKxqosIxiBQCx3Zs2d/U +GeIA7QAM2atNdXaateacEaKMmGE9LEtO0Dg5lmT43WzmGkG9NmCwK3JjAekc5S9d +HKNtEQo7o8RKfj81zlDSq2kzliy98cimk24VBBGkS2Cn7Vy/mxMCqWjQazTXbpoS +lXw6LiY5wFXQmXOB5GTSHvqyCtBQbOSSbJB77z/fm7bufTDObufTbJIq53WPt00Y +f+JNnzkX1X0MaBCUztoZwoMaExWucMe/7xsQ46hDn6KB4b0lZk+gsK45QHxvPE1R +72+ZkkIrGS/ljIKahQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDib1653CneSmy2 +gYzGeMlrI05Jqo3JuHNMQHzAjIrb4ee57VA4PTQa1ygGol/hVv6eTvZr3p2ospDS +5Kfwj1HLO4jSMX1Bnm1FG0naQogz2CD3xfYjbYOVRhAxpld1MNyRveIOhDRARY7N +XNAaNPZ1ALrwbENSYArr18xDzgGWe/dgyRCEpCFIsztiA+7jGvrmAZgceIE8K3h3 +fkvNmXBH58ZHAGTiyRriBZqS+DXrBrQOztXSJwFnOZnRt6/efeBupt8j5hxVpBLW +vtjpBc23uUcbbHOY2AW2Bf+vIr4/LmJ/MheKV+maa2990vmC93tvWlFfc74mgUkW +HJfXDmR6 -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQDiact/7HQJYoMo/k8tnzu3ZF5IMT8nlCgjlWETRtdpqF2E+tBM -LbC86lP432fMSkj0BKH96NWRgaVzTec0pEvvPJUzVTIZ24TCxGywUyp/ImEjWn5W -bEMxY2mV7HuYgCF4B2yQ+PenCLYI/hUgyM6PfrwBe9ci0/N4J8CFiERVowIDAQAB -AoGAQYTl+8XcKl8Un4dAOG6M5FwqIHAH25c3Klzu85obehrbvUCriG/sZi7VT/6u -VeLlS6APlJ+NNgczbrOLhaNJyYzjICSt8BI96PldFUzCEkVlgE+29pO7RNoZmDYB -dSGyIDrWdVYfdzpir6kC0KDcrpA16Sc+/bK6Q8ALLRpC7QECQQD7F7fhIQ03CKSk -lS4mgDuBQrB/52jXgBumtjp71ANNeaWR6+06KDPTLysM+olsh97Q7YOGORbrBnBg -Y2HPnOgjAkEA5taZaMfdFa8V1SPcX7mgCLykYIujqss0AmauZN/24oLdNE8HtTBF -OLaxE6PnQ0JWfx9KGIy3E0V3aFk5FWb0gQJBAO4KFEaXgOG1jfCBhNj3JHJseMso -5Nm4F366r0MJQYBHXNGzqphB2K/Svat2MKX1QSUspk2u/a0d05dtYCLki6UCQHWS -sChyQ+UbfF9HGKOZBC3vBzo1ZXNEdIUUj5bJjBHq3YgbCK38nAU66A482TmkvDGb -Wj4OzeB+7Ua0yyJfggECQQDVlAa8HqdAcrbEwI/YfPydFsavBJ0KtcIGK2owQ+dk -dhlDnpXDud/AtX4Ft2LaquQ15fteRrYjjwI9SFGytjtp ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDkM24IqGGXrAJu +CgKQV6bAmHcBNsCOEicem9BYgUN9IrVRISUIrGqiwjGIFALHdmzZ39QZ4gDtAAzZ +q011dpq15pwRooyYYT0sS07QODmWZPjdbOYaQb02YLArcmMB6RzlL10co20RCjuj +xEp+PzXOUNKraTOWLL3xyKaTbhUEEaRLYKftXL+bEwKpaNBrNNdumhKVfDouJjnA +VdCZc4HkZNIe+rIK0FBs5JJskHvvP9+btu59MM5u59NskirndY+3TRh/4k2fORfV +fQxoEJTO2hnCgxoTFa5wx7/vGxDjqEOfooHhvSVmT6CwrjlAfG88TVHvb5mSQisZ +L+WMgpqFAgMBAAECggEABTdPuo7uvCLIY2+DI319aEWT4sk3mYe8sSxqlLtPqZqT +fmk9iXc3cMTzkOK0NY71af19waGy17f6kzchLCAr5SCCTLzkbc87MLn/8S530oI4 +VgdZMxxxkL6hCD0zGiYT7QEqJa9unMcZGeMwuLYFKtQaHKTo8vPO26n0dMY9YLxj +cNUxsKLcKk8dbfKKt4B4fZgB7nU0BG9YbKYZ3iZ7/3mG+6jA6u+VYc/WHYQjTmpL +oLFN7NOe3R7jIx/kJ1OqNWqsFoLpyiiWd1Mr0l3EdD1kCudptMgD8hd++nx2Yk2w +K4+CpOVIN/eCxDDaAOJgYjCtOayVwUkDAxRRt9VnAQKBgQD5s1j6RJtBNTlChVxS +W3WpcG4q8933AiUY/Chx0YTyopOiTi7AGUaA8AOLFBcO2npa+vzC+lvuOyrgOtVW +sD10H2v5jNKlbeBp+Q9rux2LAyp4TvzdXWKhVyZrdtITF0hn6vEYNp7MtyWRFb1O +3Ie5HQBPHtzllFOMynacjOdjpQKBgQDp9TrbfOmwGWmwPKmaFKuy8BKxjJM+ct0X +4Xs1uSy9Z9Y8QlDNbNaooI8DA1NY0jDVHwemiGC4bYsBNKNRcbI0s2nr0hQMft42 +P/NpugHv0YXiVz+5bfim4woTiHHbfREqchlIGo3ryClAiDU9fYZwTOtb9jPIhX3G +9v+OsoMlYQKBgQDJUQW90S5zJlwh+69xXvfAQjswOimNCpeqSzK4gTn0/YqV4v7i +Nf6X2eqhaPMmMJNRYuYCtSMFMYLiAc0a9UC2rNa6/gSfB7VU+06phtTMzSKimNxa +BP6OIduB7Ox2I+Fmlw8GfJMPbeHF1YcpW7e5UV58a9+g4TNzYZC7qwarWQKBgQCA +FFaCbmHonCD18F/REFvm+/Lf7Ft3pp5PQouXH6bUkhIArzVZIKpramqgdaOdToSZ +SAGCM8rvbFja8hwurBWpMEdeaIW9SX8RJ/Vz/fateYDYJnemZgPoKQcNJnded5t8 +Jzab+J2VZODgiTDMVvnQZOu8To6OyjXPRM0nK6cMQQKBgQDyX44PHRRhEXDgJFLU +qp2ODL54Qadc/thp2m+JmAvqmCCLwuYlGpRKVkLLuZW9W6RlVqarOC3VD3wX5PRZ +IsyCGLi+Jbrv9JIrYUXE80xNeQVNhrrf02OW0KHbqGxRaNOmp1THPw98VUGR2J/q +YAp6XUXU7LEBUrowye+Ty2o7Lg== +-----END PRIVATE KEY----- """, #9 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQ3NThaFw0wOTA3MjUyMjQ3NThaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAueLfowPT -kXXtHeU2FZSz2mJhHmjqeyI1oMoyyggonccx65vMxaRfljnz2dOjVVYpCOn/LrdP -wVxHO8KNDsmQeWPRjnnBa2dFqqOnp/8gEJFJBW7K/gI9se6o+xe9QIWBq6d/fKVR -BURJe5TycLogzZuxQn1xHHILa3XleYuHAbMCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQBEC1lfC3XK0galQC96B7faLpnQmhn5lX2FUUoFIQQtBTetoE+gTqnLSOIZcOK4 -pkT3YvxUvgOV0LOLClryo2IknMMGWRSAcXtVUBBLRHVTSSuVUyyLr5kdRU7B4E+l -OU0j8Md/dzlkm//K1bzLyUaPq204ofH8su2IEX4b3IGmAQ== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNTExMVoXDTIxMDEwMTAxNTExMVowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1k2R +PWYihftppo3CoxeseFwgg7guxZVkP7aAur5uBzSeAB7sBG1G2bRrwMX71S4xPwot +zYiEoxUrTStUqEKjL2aozfHsXnHZ7kwwUgZFDZUg+ve2tZDA3HCUr4tLYKlyFqpx +2nCouc45MjQ4wAxRl4rQxIUG2uSTzvP+xXtjoJYMIEEyCpcsRXfqfVkEUe9nrPsF +0Ibzk7Cyt75HDI4uEzBuHux0DYuGy6R02jz/vf/dIZ4WepjSY06xpblTHZgieDRX +fU2+YOcvb0eDHyA8Q5p8ropK71MNIP5+kffFd90SVr4EkCA8S+cd6FdKQasRr+jF +9MUhMS4ObvlrYTG+hwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCy62MZ3+59/VpX +c9Hsmb4/BMWt0irLJit4w4SkuYKGFLCMKZI4LN4pEkXaiE1eqF2DNS1qOvl5luty +Zz4oggrqilwuFeH98o9Zeg9SYnouuypORVP/3DPbJF/jiQg5J8kJb1sy+BjRiT8I +5X6/cCBYT+MljFz5tpqWOtWTgA30e1BV8JFj8F4dgUcWsAVT/I4l9zgMLUnhcO6E +wLtEE0I6aT1RHJB28ndwJzj4La98Oirw7LAEAWbExWYB90ypLaGY+JVJe3f5fijC +fJpQ2mbs4syXDmb5bU2C2pGPTKZPcyx15iQrq1uHInD0facOw+pmllAFxuG96lA1 ++o2VzKwP -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQC54t+jA9ORde0d5TYVlLPaYmEeaOp7IjWgyjLKCCidxzHrm8zF -pF+WOfPZ06NVVikI6f8ut0/BXEc7wo0OyZB5Y9GOecFrZ0Wqo6en/yAQkUkFbsr+ -Aj2x7qj7F71AhYGrp398pVEFREl7lPJwuiDNm7FCfXEccgtrdeV5i4cBswIDAQAB -AoGAO4PnJHNaLs16AMNdgKVevEIZZDolMQ1v7C4w+ryH/JRFaHE2q+UH8bpWV9zK -A82VT9RTrqpkb71S1VBiB2UDyz263XdAI/N2HcIVMmfKb72oV4gCI1KOv4DfFwZv -tVVcIdVEDBOZ2TgqK4opGOgWMDqgIAl2z3PbsIoNylZHEJECQQDtQeJFhEJGH4Qz -BGpdND0j2nnnJyhOFHJqikJNdul3uBwmxTK8FPEUUH/rtpyUan3VMOyDx3kX4OQg -GDNSb32rAkEAyJIZIJ0EMRHVedyWsfqR0zTGKRQ+qsc3sCfyUhFksWms9jsSS0DT -tVeTdC3F6EIAdpKOGhSyfBTU4jxwbFc0GQJADI4L9znEeAl66Wg2aLA2/Aq3oK/F -xjv2wgSG9apxOFCZzMNqp+FD0Jth6YtEReZMuldYbLDFi6nu6HPfY2Fa+QJAdpm1 -lAxk6yMxiZK/5VRWoH6HYske2Vtd+aNVbePtF992ME/z3F3kEkpL3hom+dT1cyfs -MU3l0Ot8ip7Ul6vlGQJAegNzpcfl2GFSdWQMxQ+nN3woKnPqpR1M3jgnqvo7L4Xe -JW3vRxvfdrUuzdlvZ/Pbsu/vOd+cuIa4h0yD5q3N+g== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWTZE9ZiKF+2mm +jcKjF6x4XCCDuC7FlWQ/toC6vm4HNJ4AHuwEbUbZtGvAxfvVLjE/Ci3NiISjFStN +K1SoQqMvZqjN8execdnuTDBSBkUNlSD697a1kMDccJSvi0tgqXIWqnHacKi5zjky +NDjADFGXitDEhQba5JPO8/7Fe2OglgwgQTIKlyxFd+p9WQRR72es+wXQhvOTsLK3 +vkcMji4TMG4e7HQNi4bLpHTaPP+9/90hnhZ6mNJjTrGluVMdmCJ4NFd9Tb5g5y9v +R4MfIDxDmnyuikrvUw0g/n6R98V33RJWvgSQIDxL5x3oV0pBqxGv6MX0xSExLg5u ++WthMb6HAgMBAAECggEAeCyRSNwQeg/NZD/UqP6qkegft52+ZMBssinWsGH/c3z3 +KVwtwCHDfGvnjPe5TAeWSCKeIsbukkFZwfGNjLmppvgrqymCAkhYDICfDDBF4uMA +1pu40sJ01Gkxh+tV/sOmnb1BEVzh0Sgq/NM6C8ActR18CugKOw+5L3G2KeoSqUbT +2hcPUsnik10KwqW737GQW4LtEQEr/iRmQkxI3+HBzvPWjFZzjOcpUph+FW5TXtaU +T26mt1j+FjbdvvhCuRMY/VZBJ5h1RKU95r57F1AjW/C0RRJ8FxR1CeSy4IlmQBrh +6wAa3Tdm0k/n4ZspC9bF5eVTJEtb0AohiYZrIa8MuQKBgQD8yjCLYa41H304odCx +NwPRJcmlIk5YGxPrhHAT9GEgU6n/no7YMVx1L7fNLcMjAyx54jauEU7J19Aki7eV +SIdU9TwqmkOAFfM6TOEJZiOi66gABOxeK2yDyfmR6Apaw3caku4O058t4KVwHSCB +DanYCMzxCBqS9jUTTyAh0fMg6wKBgQDZBkIukg3FKPor5LzkUXIKnNHYPfHbERHw +piWS6GZwqhuWNlOCWxiBR4rEUU/RbFQZw/FCi5OuAk2lBC0LBmC0/Sz4/+xDdCbv +uNhMOTRcy9nFVpmpIWCx4N/KmXHEuFxli/JNXux7iki74AVC9VPrAt/kCvwf06Df +oDb8ljdR1QKBgQChVOD6c5Lc8IXYeN1Z3IShHH6+11AsxstFyjZFZff+y6Z5L1Z2 +/7nESHoDhqs9Uy81cnv3R7CC/Ssnx8uYiLtmK0UE44Mk4d1jXeFZQEiKF+AWcw3v +Y8NTsLmItxC0sH75BMDN0Z2LiA3Nqaku8+trpuI1Cjj7hgqFkkAtlXKXlQKBgBMb +c/Q5s7CqHOyEZQUNDqdUiz0opwSMijHPzvsSLwK4V1lwSwXtE0k+jT8fkZF0oirq +j3E2bLgjR8bBiV2xIA6PQ8hgb+K4dT0h3xlG6A9Le07egwTbBXJjxBBIVjXlrWzb +V2fsdZGi6ShxXsU4aD0GscOYG/6JWV6W8oBmkVRJAoGAepIZ+OYmFjb7uxdh4EtP +hluEtx5bLOLuo6c0S149omUXUhbsuyzTZS6Ip9ySDMnK3954c4Q4WJ4yQKixQNVq +78aDfy4hP/8TE/Q9CRddUof2P33PJMhVNqzTRYMpqV+zxifvtw3hoDTLKHTQxCR2 +M1+O4VvokU5pBqUpGXiMDfs= +-----END PRIVATE KEY----- """, #10 """-----BEGIN CERTIFICATE----- -MIIBnjCCAQcCAgCEMA0GCSqGSIb3DQEBBAUAMBcxFTATBgNVBAMUDG5ld3BiX3Ro -aW5neTAeFw0wODA3MjUyMjQ3NThaFw0wOTA3MjUyMjQ3NThaMBcxFTATBgNVBAMU -DG5ld3BiX3RoaW5neTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAruBhwk+J -XdlwfKXXN8K+43JyEYCV7Fp7ZiES4t4AEJuQuBqJVMxpzeZzu2t/vVb59ThaxxtY -NGD3Xy6Og5dTv//ztWng8P7HwwvfbrUICU6zo6JAhg7kfaNa116krCYOkC/cdJWt -o5W+zsDmI1jUVGH0D73h29atc1gn6wLpAsMCAwEAATANBgkqhkiG9w0BAQQFAAOB -gQAEJ/ITGJ9lK/rk0yHcenW8SHsaSTlZMuJ4yEiIgrJ2t71Rd6mtCC/ljx9USvvK -bF500whTiZlnWgKi02boBEKa44z/DytF6pljeNPefBQSqZyUByGEb/8Mn58Idyls -q4/d9iKXMPvbpQdcesOzgOffFZevLQSWyPRaIdYBOOiYUA== +MIICojCCAYoCAQEwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMbmV3cGJfdGhp +bmd5MB4XDTIwMDEwMjAxNTExMVoXDTIxMDEwMTAxNTExMVowFzEVMBMGA1UEAwwM +bmV3cGJfdGhpbmd5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnbCU +M37hG7zrCyyJEI6pZmOomnI+CozbP5KAhWSV5y7R5H6lcAEG2UDV+lCUxHT2ufOa +i1H16bXyBt7VoMTHIH50S58NUCUEXcuRWVR16tr8CzcTHQAkfIrmhY2XffPilX7h +aw35UkoVmXcqSDNNJD6jmvWexvmbhzVWW8Vt5Pivet2/leVuqPXB54/alSbkC74m +x6X5XKQc6eyPsb1xvNBuiSpFzdqbEn7lUwj6jFTkh9tlixgmgx+J0XoQXbawyrAg +rcIQcse/Ww+KBA1KSccFze+XBTbIull4boYhbJqkb6DW5bY7/me2nNxE9DRGwq+S +kBsKq3YKeCf8LEhfqQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAD+tWGFhINYsWT +ibKWlCGgBc5uB7611cLCevx1yAL6SaOECVCQXzaaXIaETSbyY03UO2yBy3Pl10FV +GYXLrAWTFZsNVJm55XIibTNw1UBPNwdIoCSzAYuOgMF0GHhTTQU0hNYWstOnnE2T +6lSAZQZFkaW4ZKs6sUp42Em9Bu99PehyIgnw14qb9NPg5qKdi2GAvkImZCrGpMdK +OF31U7Ob0XQ0lxykcNgG4LlUACd+QxLfNpmLBZUGfikexYa1VqBFm3oAvTt8ybNQ +qr7AKXDFnW75aCBaMpQWzrstA7yYZ3D9XCd5ZNf6d08lGM/oerDAIGnZOZPJgs5U +FaWPHdS9 -----END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQCu4GHCT4ld2XB8pdc3wr7jcnIRgJXsWntmIRLi3gAQm5C4GolU -zGnN5nO7a3+9Vvn1OFrHG1g0YPdfLo6Dl1O///O1aeDw/sfDC99utQgJTrOjokCG -DuR9o1rXXqSsJg6QL9x0la2jlb7OwOYjWNRUYfQPveHb1q1zWCfrAukCwwIDAQAB -AoGAcZAXC/dYrlBpIxkTRQu7qLqGZuVI9t7fabgqqpceFargdR4Odrn0L5jrKRer -MYrM8bjyAoC4a/NYUUBLnhrkcCQWO9q5fSQuFKFVWHY53SM63Qdqk8Y9Fmy/h/4c -UtwZ5BWkUWItvnTMgb9bFcvSiIhEcNQauypnMpgNknopu7kCQQDlSQT10LkX2IGT -bTUhPcManx92gucaKsPONKq2mP+1sIciThevRTZWZsxyIuoBBY43NcKKi8NlZCtj -hhSbtzYdAkEAw0B93CXfso8g2QIMj/HJJz/wNTLtg+rriXp6jh5HWe6lKWRVrce+ -1w8Qz6OI/ZP6xuQ9HNeZxJ/W6rZPW6BGXwJAHcTuRPA1p/fvUvHh7Q/0zfcNAbkb -QlV9GL/TzmNtB+0EjpqvDo2g8XTlZIhN85YCEf8D5DMjSn3H+GMHN/SArQJBAJlW -MIGPjNoh5V4Hae4xqBOW9wIQeM880rUo5s5toQNTk4mqLk9Hquwh/MXUXGUora08 -2XGpMC1midXSTwhaGmkCQQCdivptFEYl33PrVbxY9nzHynpp4Mi89vQF0cjCmaYY -N8L+bvLd4BU9g6hRS8b59lQ6GNjryx2bUnCVtLcey4Jd ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdsJQzfuEbvOsL +LIkQjqlmY6iacj4KjNs/koCFZJXnLtHkfqVwAQbZQNX6UJTEdPa585qLUfXptfIG +3tWgxMcgfnRLnw1QJQRdy5FZVHXq2vwLNxMdACR8iuaFjZd98+KVfuFrDflSShWZ +dypIM00kPqOa9Z7G+ZuHNVZbxW3k+K963b+V5W6o9cHnj9qVJuQLvibHpflcpBzp +7I+xvXG80G6JKkXN2psSfuVTCPqMVOSH22WLGCaDH4nRehBdtrDKsCCtwhByx79b +D4oEDUpJxwXN75cFNsi6WXhuhiFsmqRvoNbltjv+Z7ac3ET0NEbCr5KQGwqrdgp4 +J/wsSF+pAgMBAAECggEAPSu1ofBTRN5ZU4FYPlsJLdX1Hsy4coFHv/aF8rkdSYwp +EflrFfLgBEEZgLvnqfoxh9sPFYKa4amaFL42ouIS2PEVDgzKLk/dzMDeRof0IkIG +yhb4TCS1ArcjS6WsociNGi8ZJN1L3Xctv9WxSkbUYv4Fm2Qyzr8fbSjssjb5NXwD +K11fsj6Pfy/mQrI0TSTlzWC7ARIlCMTWQ8G8zEU6bMFIG6DMjt2J4VgYVXUKetZA +VPuS+pwkH2obQe6FLRuiNxH4GitVAASYPea6foER4AggBMRp8q8F6+WssjoyEORb +0sJxmnxhznoTRMCuTsUj6XMgmOTOnA3lQXsIB0DYcQKBgQDO6mMRVVFWzgkE9Q5/ +36n06KvGYF9TCRDL9vRC8kCqcGd1Hy6jRj0D8049KUHaN74pfWg6gsQjPkKzwKnC +vxNl72tVvLqm7Fo531BGfKK/46ZvxeWMMraNW4+9LhwMPu2LN5OEdwwCgyaURpxh +ktCp+RrGjz08Kn82X1jJPdwxDQKBgQDDGMvZ7ZUDGq5+RJkmHJ58lQtiaMZclmYV +R9YwOxJV6ino3EYrGOtUkqiemgAACdMWE/JMJlB1/JINawJwUsZ2XDp/9jNLPgLc +gphCmagaO34U/YMaJbJIK2gkCX7p8EcD+x45qWa0bEMPW38QfN/qQdUPjNmpuIiI +Zleyl1TqDQKBgQCvIoat0ighsAzETGN0aqzhJdrW8xVcJA06hpFi5MdFPBTldno0 +KqxUXqj3badWe94SIhqJg8teBUHSAZ3uv2o82nRgQnk99km8OD8rGi1q+9YRP1C2 +5OnNJhW4y4FkABNxxZ2v/k+FBNsvn8CXefvyEm3OaMks1s+MBxIQa7KnNQKBgFwX +HUo+GiN/+bPCf6P8yFa4J8qI+HEF0SPkZ9cWWx5QzP2M1FZNie++1nce7DcYbBo0 +yh9lyn8W/H328AzDFckS2c5DEY1HtSQPRP3S+AWB5Y7U54h1GMV2L88q6ExWzb60 +T10aeE9b9v+NydmniC5UatTPQIMbht8Tp/u18TAVAoGBAJphAfiqWWV2M5aBCSXq +WxLZ71AJ0PZBmRa/9iwtccwXQpMcW6wHK3YSQxci+sB97TElRa3/onlVSpohrUtg +VCvCwfSHX1LmrfWNSkoJZwCQt+YYuMqW86K0tzLzI1EMjIH9LgQvB6RR26PZQs+E +jr1ZvRc+wPTq6sxCF1h9ZAfN +-----END PRIVATE KEY----- """, #11 ] # To disable the pre-computed tub certs, uncomment this line. -#SYSTEM_TEST_CERTS = [] +# SYSTEM_TEST_CERTS = [] def flush_but_dont_ignore(res): d = flushEventualQueue() @@ -421,9 +649,6 @@ def _render_section_values(values): class SystemTestMixin(pollmixin.PollMixin, testutil.StallMixin): - if 'pypy' in sys.version.lower(): - skip = "pypy can't run these, due to SSL errors (ee key too tiny)" - def setUp(self): self.port_assigner = SameProcessStreamEndpointAssigner() self.port_assigner.setUp() From b54dc1e5d53243c59a254068bb597702feafad96 Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 5 May 2020 13:56:59 -0600 Subject: [PATCH 0063/1054] whitespace (1 line per import) --- src/allmydata/test/test_storage.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 7e4eaa3b6..baf6c6d4b 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -1,4 +1,11 @@ -import time, os.path, platform, stat, re, json, struct, shutil +import time +import os.path +import platform +import stat +import re +import json +import struct +import shutil from twisted.trial import unittest From d4f29e7d6c5454f49e9206893638383892f5e69c Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 5 May 2020 13:57:08 -0600 Subject: [PATCH 0064/1054] restore old code, run the GC --- src/allmydata/test/test_storage.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index baf6c6d4b..73c183a38 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -6,6 +6,7 @@ import re import json import struct import shutil +import gc from twisted.trial import unittest @@ -561,8 +562,10 @@ class Server(unittest.TestCase): # we abandon the first set, so their provisional allocation should be # returned - for x in writers.values(): - x.remote_close() + del already + del writers + gc.collect() + self.failUnlessEqual(len(ss._active_writers), 1) # now we have a provisional allocation of 1001 bytes @@ -586,8 +589,10 @@ class Server(unittest.TestCase): self.failUnlessEqual(len(writers3), 39) self.failUnlessEqual(len(ss._active_writers), 39) - for x in writers3.values(): - x._disconnected() + del already3 + del writers3 + gc.collect() + self.failUnlessEqual(len(ss._active_writers), 0) ss.disownServiceParent() del ss From 6eade15b4edd59764b0a5a3c13d4b26a08d764d1 Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 5 May 2020 14:49:16 -0600 Subject: [PATCH 0065/1054] put WeakKeyDictionary use back --- src/allmydata/storage/server.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/allmydata/storage/server.py b/src/allmydata/storage/server.py index 5cc4be95c..26823957e 100644 --- a/src/allmydata/storage/server.py +++ b/src/allmydata/storage/server.py @@ -1,4 +1,5 @@ import os, re, struct, time +import weakref import six from foolscap.api import Referenceable @@ -67,7 +68,7 @@ class StorageServer(service.MultiService, Referenceable): self.incomingdir = os.path.join(sharedir, 'incoming') self._clean_incomplete() fileutil.make_dirs(self.incomingdir) - self._active_writers = set() + self._active_writers = weakref.WeakKeyDictionary() log.msg("StorageServer created", facility="tahoe.storage") if reserved_space: @@ -302,7 +303,7 @@ class StorageServer(service.MultiService, Referenceable): if self.no_storage: bw.throw_out_all_data = True bucketwriters[shnum] = bw - self._active_writers.add(bw) + self._active_writers[bw] = 1 if limited: remaining_space -= max_space_per_bucket else: @@ -358,7 +359,7 @@ class StorageServer(service.MultiService, Referenceable): def bucket_writer_closed(self, bw, consumed_size): if self.stats_provider: self.stats_provider.count('storage_server.bytes_added', consumed_size) - self._active_writers.remove(bw) + del self._active_writers[bw] def _get_bucket_shares(self, storage_index): """Return a list of (shnum, pathname) tuples for files that hold From 1602c6625006a7b8f2ad439fbe1f5e1b32898f76 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 5 May 2020 17:38:08 -0400 Subject: [PATCH 0066/1054] Render map update status using twisted.web.template Fixes: ticket:3291 --- newsfragments/3291.minor | 0 src/allmydata/web/map-update-status.xhtml | 50 ++++---- src/allmydata/web/status.py | 136 ++++++++++++++-------- 3 files changed, 113 insertions(+), 73 deletions(-) create mode 100644 newsfragments/3291.minor diff --git a/newsfragments/3291.minor b/newsfragments/3291.minor new file mode 100644 index 000000000..e69de29bb diff --git a/src/allmydata/web/map-update-status.xhtml b/src/allmydata/web/map-update-status.xhtml index 25b6a28e0..603982623 100644 --- a/src/allmydata/web/map-update-status.xhtml +++ b/src/allmydata/web/map-update-status.xhtml @@ -1,35 +1,39 @@ - + Tahoe-LAFS - Mutable File Servermap Update Status + -

        Mutable File Servermap Update Status

        +

        Mutable File Servermap Update Status

        -
          -
        • Started:
        • -
        • Finished:
        • -
        • Storage Index:
        • -
        • Helper?:
        • -
        • Progress:
        • -
        • Status:
        • -
        +
          +
        • Started:
        • +
        • Finished:
        • +
        • Storage Index:
        • +
        • Helper?:
        • +
        • Progress:
        • +
        • Status:
        • +
        -

        Update Results

        -
          -
        • -
        • Total:
        • -
            -
          • Initial Queries:
          • -
          • -
          • Cumulative Verify:
          • -
          -
        • -
        +

        Update Results

        -
        Return to the Welcome Page
        +
          +
        • +
        • Total:
        • +
            +
          • Initial Queries:
          • +
          • +
          • Cumulative Verify:
          • +
          +
        • +
        - + +
        Return to the Welcome Page
        + + + diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index bc0d69d77..353022174 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -9,7 +9,7 @@ from twisted.web.template import ( XMLFile, renderer, renderElement, - tags + tags, ) from nevow import rend, tags as T from allmydata.util import base32, idlib @@ -868,73 +868,109 @@ class PublishStatusPage(rend.Page, RateAndTimeMixin): l[T.li["[%s]: %s" % (server.get_name(), times_s)]] return T.li["Per-Server Response Times: ", l] -class MapupdateStatusPage(rend.Page, RateAndTimeMixin): - docFactory = getxmlfile("map-update-status.xhtml") - def __init__(self, data): - rend.Page.__init__(self, data) - self.update_status = data +class MapupdateStatusPage(MultiFormatResource): - def render_started(self, ctx, data): - started_s = render_time(data.get_started()) - return started_s + def __init__(self, update_status): + super(MapupdateStatusPage, self).__init__() + self._update_status = update_status - def render_finished(self, ctx, data): - when = data.get_finished() + def render_HTML(self, req): + elem = MapupdateStatusElement(self._update_status); + return renderElement(req, elem) + + +class MapupdateStatusElement(RateAndTimeMixin, Element): + + loader = XMLFile(FilePath(__file__).sibling("map-update-status.xhtml")) + + def __init__(self, update_status): + super(MapupdateStatusElement, self).__init__() + self._update_status = update_status + + @renderer + def started(self, req, tag): + started_s = render_time(self._update_status.get_started()) + return tag(started_s) + + @renderer + def finished(self, req, tag): + when = self._update_status.get_finished() if not when: - return "not yet" - started_s = render_time(data.get_finished()) - return started_s + return tag("not yet") + started_s = render_time(self._update_status.get_finished()) + return tag(started_s) - def render_si(self, ctx, data): - si_s = base32.b2a_or_none(data.get_storage_index()) + @renderer + def si(self, req, tag): + si_s = base32.b2a_or_none(self._update_status.get_storage_index()) if si_s is None: si_s = "(None)" - return si_s + return tag(si_s) - def render_helper(self, ctx, data): - return {True: "Yes", - False: "No"}[data.using_helper()] + @renderer + def helper(self, req, tag): + return tag({True: "Yes", + False: "No"}[self._update_status.using_helper()]) - def render_progress(self, ctx, data): - progress = data.get_progress() + @renderer + def progress(self, req, tag): + progress = self._update_status.get_progress() # TODO: make an ascii-art bar - return "%.1f%%" % (100.0 * progress) + return tag("%.1f%%" % (100.0 * progress)) - def render_status(self, ctx, data): - return data.get_status() + @renderer + def status(self, req, tag): + return tag(self._update_status.get_status()) - def render_problems(self, ctx, data): - problems = data.problems + @renderer + def problems(self, req, tag): + problems = self._update_status.problems if not problems: - return "" - l = T.ul() + return tag("") + l = tags.ul() for peerid in sorted(problems.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) - l[T.li["[%s]: %s" % (peerid_s, problems[peerid])]] - return ctx.tag["Server Problems:", l] + l(tags.li("[%s]: %s" % (peerid_s, problems[peerid]))) + return tag("Server Problems:", l) - def render_privkey_from(self, ctx, data): - server = data.get_privkey_from() + @renderer + def privkey_from(self, req, tag): + server = self._update_status.get_privkey_from() if server: - return ctx.tag["Got privkey from: [%s]" % server.get_name()] + return tag("Got privkey from: [%s]" % server.get_name()) else: - return "" + return tag("") - def data_time_total(self, ctx, data): - return self.update_status.timings.get("total") + # Helper to query update status timings. + # + # Querying `update_status.timings` can yield `None` or a numeric + # value, but twisted.web has trouble flattening the element tree + # when a node contains numeric values. Stringifying them helps. + def _get_update_status_timing(self, name, tag): + res = self._update_status.timings.get(name) + if not res: + return tag("") + return tag(str(res)) - def data_time_initial_queries(self, ctx, data): - return self.update_status.timings.get("initial_queries") + @renderer + def time_total(self, req, tag): + return self._get_update_status_timing("total", tag) - def data_time_cumulative_verify(self, ctx, data): - return self.update_status.timings.get("cumulative_verify") + @renderer + def time_initial_queries(self, req, tag): + return self._get_update_status_timing("initial_queries", tag) - def render_server_timings(self, ctx, data): - per_server = self.update_status.timings.get("per_server") + @renderer + def time_cumulative_verify(self, req, tag): + return self._get_update_status_timing("cumulative_verify", tag) + + @renderer + def server_timings(self, req, tag): + per_server = self._update_status.timings.get("per_server") if not per_server: - return "" - l = T.ul() + return tag("") + l = tags.ul() for server in sorted(per_server.keys(), key=lambda s: s.get_name()): times = [] for op,started,t in per_server[server]: @@ -943,14 +979,14 @@ class MapupdateStatusPage(rend.Page, RateAndTimeMixin): # self.render_time(None, started - self.update_status.get_started()), # self.render_time(None,t))) if op == "query": - times.append( self.render_time(None, t) ) + times.append(self.render_time(None, t)) elif op == "late": - times.append( "late(" + self.render_time(None, t) + ")" ) + times.append("late(" + self.render_time(None, t) + ")") else: - times.append( "privkey(" + self.render_time(None, t) + ")" ) + times.append("privkey(" + self.render_time(None, t) + ")") times_s = ", ".join(times) - l[T.li["[%s]: %s" % (server.get_name(), times_s)]] - return T.li["Per-Server Response Times: ", l] + l(tags.li("[%s]: %s" % (server.get_name(), times_s))) + return tags.li("Per-Server Response Times: ", l) def marshal_json(s): From 86871363ea61609f18061f03c3d993ec0577efa9 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 08:44:31 -0400 Subject: [PATCH 0067/1054] Add notes to MapupdateStatusPage --- src/allmydata/web/status.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 353022174..cd23e9ff5 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -869,9 +869,13 @@ class PublishStatusPage(rend.Page, RateAndTimeMixin): return T.li["Per-Server Response Times: ", l] +# Renders "/status/mapupdate-%d" class MapupdateStatusPage(MultiFormatResource): def __init__(self, update_status): + """ + :update_status servermap.UpdateStatus: server map stats provider. + """ super(MapupdateStatusPage, self).__init__() self._update_status = update_status From 8a71567137249d7310d436adaeece97ab0c2bc7f Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 09:07:32 -0400 Subject: [PATCH 0068/1054] Avoid use of RateAndTimeMixin in MapupdateStatusPage --- src/allmydata/web/status.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index cd23e9ff5..e8becea98 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -884,7 +884,7 @@ class MapupdateStatusPage(MultiFormatResource): return renderElement(req, elem) -class MapupdateStatusElement(RateAndTimeMixin, Element): +class MapupdateStatusElement(Element): loader = XMLFile(FilePath(__file__).sibling("map-update-status.xhtml")) @@ -983,11 +983,11 @@ class MapupdateStatusElement(RateAndTimeMixin, Element): # self.render_time(None, started - self.update_status.get_started()), # self.render_time(None,t))) if op == "query": - times.append(self.render_time(None, t)) + times.append(abbreviate_time(t)) elif op == "late": - times.append("late(" + self.render_time(None, t) + ")") + times.append("late(" + abbreviate_time(t) + ")") else: - times.append("privkey(" + self.render_time(None, t) + ")") + times.append("privkey(" + abbreviate_time(t) + ")") times_s = ", ".join(times) l(tags.li("[%s]: %s" % (server.get_name(), times_s))) return tags.li("Per-Server Response Times: ", l) From bbbca6c0001eed27f3d5300086031184126f362b Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 10:41:58 -0400 Subject: [PATCH 0069/1054] Drop a newline Party for consistency, partly in order to trigger a CircleCI run. --- src/allmydata/web/map-update-status.xhtml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/allmydata/web/map-update-status.xhtml b/src/allmydata/web/map-update-status.xhtml index 603982623..3bfd53dd1 100644 --- a/src/allmydata/web/map-update-status.xhtml +++ b/src/allmydata/web/map-update-status.xhtml @@ -32,7 +32,6 @@
      -
      Return to the Welcome Page
      From 0c6889274da9fb5f1eb2538bac7565cb67366045 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 13:10:31 -0400 Subject: [PATCH 0070/1054] Render problems only when there's a problem list --- src/allmydata/web/map-update-status.xhtml | 2 +- src/allmydata/web/status.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/map-update-status.xhtml b/src/allmydata/web/map-update-status.xhtml index 3bfd53dd1..3b963b11d 100644 --- a/src/allmydata/web/map-update-status.xhtml +++ b/src/allmydata/web/map-update-status.xhtml @@ -22,7 +22,7 @@

      Update Results

        -
      • +
      • Total:
        • Initial Queries:
        • diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index e8becea98..4c79e2b25 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -931,7 +931,7 @@ class MapupdateStatusElement(Element): def problems(self, req, tag): problems = self._update_status.problems if not problems: - return tag("") + return tag l = tags.ul() for peerid in sorted(problems.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) From 008812d6c8747bbda753bbf69ab24e4b273817f3 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 13:11:53 -0400 Subject: [PATCH 0071/1054] Render abbreviated time in map update results --- src/allmydata/web/status.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 4c79e2b25..4755ce445 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -944,7 +944,7 @@ class MapupdateStatusElement(Element): if server: return tag("Got privkey from: [%s]" % server.get_name()) else: - return tag("") + return tag # Helper to query update status timings. # @@ -954,8 +954,8 @@ class MapupdateStatusElement(Element): def _get_update_status_timing(self, name, tag): res = self._update_status.timings.get(name) if not res: - return tag("") - return tag(str(res)) + return tag("0") + return tag(abbreviate_time(res)) @renderer def time_total(self, req, tag): From 13bbda15291ea120a875b11f37055420c6913af7 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 13:21:26 -0400 Subject: [PATCH 0072/1054] Render privkey only when there's a privkey --- src/allmydata/web/map-update-status.xhtml | 2 +- src/allmydata/web/status.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/map-update-status.xhtml b/src/allmydata/web/map-update-status.xhtml index 3b963b11d..445c0b470 100644 --- a/src/allmydata/web/map-update-status.xhtml +++ b/src/allmydata/web/map-update-status.xhtml @@ -26,7 +26,7 @@
        • Total:
          • Initial Queries:
          • -
          • +
          • Cumulative Verify:
        • diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 4755ce445..eaf0f726e 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -942,7 +942,7 @@ class MapupdateStatusElement(Element): def privkey_from(self, req, tag): server = self._update_status.get_privkey_from() if server: - return tag("Got privkey from: [%s]" % server.get_name()) + return tag(tags.li("Got privkey from: [%s]" % server.get_name())) else: return tag From 1549e7eb18600832a3a7699f7fdbf9e975133f4b Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 6 May 2020 15:15:19 -0600 Subject: [PATCH 0073/1054] simplify (for line in f.readlines() -> for line in f) --- src/allmydata/blacklist.py | 2 +- src/allmydata/control.py | 2 +- src/allmydata/frontends/auth.py | 2 +- src/allmydata/scripts/common.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/allmydata/blacklist.py b/src/allmydata/blacklist.py index af1d185d0..89ee81a96 100644 --- a/src/allmydata/blacklist.py +++ b/src/allmydata/blacklist.py @@ -35,7 +35,7 @@ class Blacklist(object): if self.last_mtime is None or current_mtime > self.last_mtime: self.entries.clear() with open(self.blacklist_fn, "r") as f: - for line in f.readlines(): + for line in f: line = line.strip() if not line or line.startswith("#"): continue diff --git a/src/allmydata/control.py b/src/allmydata/control.py index 55f6db4f9..b4603b2ac 100644 --- a/src/allmydata/control.py +++ b/src/allmydata/control.py @@ -20,7 +20,7 @@ def get_memory_usage(): stats = {} try: with open("/proc/self/status", "r") as f: - for line in f.readlines(): + for line in f: name, right = line.split(":",2) if name in stat_names: assert right.endswith(" kB\n") diff --git a/src/allmydata/frontends/auth.py b/src/allmydata/frontends/auth.py index ab56bf94d..1bd481321 100644 --- a/src/allmydata/frontends/auth.py +++ b/src/allmydata/frontends/auth.py @@ -32,7 +32,7 @@ class AccountFileChecker(object): self.pubkeys = {} self.rootcaps = {} with open(abspath_expanduser_unicode(accountfile), "r") as f: - for line in f.readlines(): + for line in f: line = line.strip() if line.startswith("#") or not line: continue diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index 89c1f94d1..91a3882fb 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -139,7 +139,7 @@ def get_aliases(nodedir): pass try: with codecs.open(aliasfile, "r", "utf-8") as f: - for line in f.readlines(): + for line in f: line = line.strip() if line.startswith("#") or not line: continue From 2418e90bf0d4e462f53cdd74e7a8dc2fb7d85efd Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 6 May 2020 15:22:22 -0600 Subject: [PATCH 0074/1054] CacheDirectoryManager is unused --- src/allmydata/test/test_util.py | 70 +-------------------------------- src/allmydata/util/cachedir.py | 43 -------------------- 2 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 src/allmydata/util/cachedir.py diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index ca05e4008..3bddc8bcc 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -19,7 +19,7 @@ from twisted.python import log from allmydata.util import base32, idlib, humanreadable, mathutil, hashutil from allmydata.util import assertutil, fileutil, deferredutil, abbreviate -from allmydata.util import limiter, time_format, pollmixin, cachedir +from allmydata.util import limiter, time_format, pollmixin from allmydata.util import statistics, dictutil, pipeline, yamlutil from allmydata.util import log as tahoe_log from allmydata.util.spans import Spans, overlap, DataSpans @@ -1286,74 +1286,6 @@ class TimeFormat(unittest.TestCase, TimezoneMixin): self.failUnlessEqual( time_format.format_delta(time_1decimal, time_1d21h46m49s_delta), '1d 21h 46m 48s') -class CacheDir(unittest.TestCase): - def test_basic(self): - basedir = "test_util/CacheDir/test_basic" - - def _failIfExists(name): - absfn = os.path.join(basedir, name) - self.failIf(os.path.exists(absfn), - "%s exists but it shouldn't" % absfn) - - def _failUnlessExists(name): - absfn = os.path.join(basedir, name) - self.failUnless(os.path.exists(absfn), - "%s doesn't exist but it should" % absfn) - - cdm = cachedir.CacheDirectoryManager(basedir) - a = cdm.get_file("a") - b = cdm.get_file("b") - c = cdm.get_file("c") - for x in {a, b, c}: - with open(x.get_filename(), "wb") as f: - f.write("hi") - del x - gc.collect() # for PyPy - - _failUnlessExists("a") - _failUnlessExists("b") - _failUnlessExists("c") - - cdm.check() - - _failUnlessExists("a") - _failUnlessExists("b") - _failUnlessExists("c") - - del a - gc.collect() # for PyPy - # this file won't be deleted yet, because it isn't old enough - cdm.check() - _failUnlessExists("a") - _failUnlessExists("b") - _failUnlessExists("c") - - # we change the definition of "old" to make everything old - cdm.old = -10 - - cdm.check() - _failIfExists("a") - _failUnlessExists("b") - _failUnlessExists("c") - - cdm.old = 60*60 - - del b - gc.collect() # for PyPy - - cdm.check() - _failIfExists("a") - _failUnlessExists("b") - _failUnlessExists("c") - - b2 = cdm.get_file("b") - - cdm.check() - _failIfExists("a") - _failUnlessExists("b") - _failUnlessExists("c") - del b2 - gc.collect() # for PyPy ctr = [0] class EqButNotIs(object): diff --git a/src/allmydata/util/cachedir.py b/src/allmydata/util/cachedir.py deleted file mode 100644 index 118a8a7eb..000000000 --- a/src/allmydata/util/cachedir.py +++ /dev/null @@ -1,43 +0,0 @@ - -import os.path, stat, weakref, time -from twisted.application import service, internet -from allmydata.util import fileutil - -HOUR = 60*60 - -class CacheDirectoryManager(service.MultiService): - def __init__(self, basedir, pollinterval=1*HOUR, old=1*HOUR): - service.MultiService.__init__(self) - self.basedir = basedir - fileutil.make_dirs(basedir) - self.old = old - self.files = weakref.WeakValueDictionary() - - t = internet.TimerService(pollinterval, self.check) - t.setServiceParent(self) - - def get_file(self, key): - assert isinstance(key, str) # used as filename - absfn = os.path.join(self.basedir, key) - if os.path.exists(absfn): - os.utime(absfn, None) - cf = CacheFile(absfn) - self.files[key] = cf - return cf - - def check(self): - now = time.time() - for fn in os.listdir(self.basedir): - if fn in self.files: - continue - absfn = os.path.join(self.basedir, fn) - mtime = os.stat(absfn)[stat.ST_MTIME] - if now - mtime > self.old: - os.remove(absfn) - -class CacheFile(object): - def __init__(self, absfn): - self.filename = absfn - - def get_filename(self): - return self.filename From 825bc91256fd12ce16df21cb968f5670e6acef1b Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 6 May 2020 17:17:23 -0600 Subject: [PATCH 0075/1054] mark PyPy as required in circle-ci --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61ed12a5d..d0f596fc7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -207,7 +207,6 @@ jobs: environment: <<: *UTF_8_ENVIRONMENT TAHOE_LAFS_TOX_ENVIRONMENT: "pypy27-coverage" - ALLOWED_FAILURE: "yes" c-locale: From d2375e4fe20d1a1b8cb6e67b71083869f36092bd Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 6 May 2020 22:42:32 -0400 Subject: [PATCH 0076/1054] Reformat retrieve status page template --- src/allmydata/web/retrieve-status.xhtml | 63 +++++++++++++------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/allmydata/web/retrieve-status.xhtml b/src/allmydata/web/retrieve-status.xhtml index f586429f5..0b060d38b 100644 --- a/src/allmydata/web/retrieve-status.xhtml +++ b/src/allmydata/web/retrieve-status.xhtml @@ -1,43 +1,48 @@ + Tahoe-LAFS - Mutable File Retrieve Status + -

          Mutable File Retrieve Status

          +

          Mutable File Retrieve Status

          -
            -
          • Started:
          • -
          • Storage Index:
          • -
          • Helper?:
          • -
          • Current Size:
          • -
          • Progress:
          • -
          • Status:
          • -
          - -

          Retrieve Results

          -
            -
          • -
          • -
          • Timings:
          • -
              -
            • Total: - ()
              • -
              • Fetching: - ()
              • -
              • Decoding: - ()
              • -
              • Decrypting: - ()
              • +
              • Started:
              • +
              • Storage Index:
              • +
              • Helper?:
              • +
              • Current Size:
              • +
              • Progress:
              • +
              • Status:
              -
            • -
            -
          -
          Return to the Welcome Page
          +

          Retrieve Results

          - +
            +
          • +
          • +
          • Timings:
          • +
              +
            • Total: + ()
            • +
                +
              • Fetching: + ()
              • +
              • Decoding: + ()
              • +
              • Decrypting: + ()
              • +
              +
            • +
            +
          + +
          Return to the Welcome Page
          + + + + From 5fa7c4c446a228e68be28b560b4ebbcfc69d57cd Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 7 May 2020 09:32:01 -0400 Subject: [PATCH 0077/1054] Update markup in retrieve status template page --- src/allmydata/web/retrieve-status.xhtml | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/allmydata/web/retrieve-status.xhtml b/src/allmydata/web/retrieve-status.xhtml index 0b060d38b..04e08af87 100644 --- a/src/allmydata/web/retrieve-status.xhtml +++ b/src/allmydata/web/retrieve-status.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - Mutable File Retrieve Status @@ -12,32 +12,32 @@

          Mutable File Retrieve Status

            -
          • Started:
          • -
          • Storage Index:
          • -
          • Helper?:
          • -
          • Current Size:
          • -
          • Progress:
          • -
          • Status:
          • +
          • Started:
          • +
          • Storage Index:
          • +
          • Helper?:
          • +
          • Current Size:
          • +
          • Progress:
          • +
          • Status:

          Retrieve Results

            -
          • -
          • +
          • +
          • Timings:
            • -
            • Total: - ()
            • +
            • Total: + ()
              • -
              • Fetching: - ()
              • -
              • Decoding: - ()
              • -
              • Decrypting: - ()
              • +
              • Fetching: + ()
              • +
              • Decoding: + ()
              • +
              • Decrypting: + ()
              -
            • +
          From de4130274374cb5ce9d8f00ba60c01f1b2e6ed3e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 7 May 2020 09:34:42 -0400 Subject: [PATCH 0078/1054] Update RetrieveStatusPage to use twisted renderers --- src/allmydata/web/status.py | 144 ++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 55 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index bc0d69d77..60e2a0a70 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -673,90 +673,124 @@ class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page): def render_status(self, ctx, data): return data.get_status() -class RetrieveStatusPage(rend.Page, RateAndTimeMixin): - docFactory = getxmlfile("retrieve-status.xhtml") - def __init__(self, data): - rend.Page.__init__(self, data) - self.retrieve_status = data +class RetrieveStatusPage(MultiFormatResource): - def render_started(self, ctx, data): - started_s = render_time(data.get_started()) - return started_s + def __init__(self, retrieve_status): + super(RetrieveStatusPage, self).__init__() + self._retrieve_status = retrieve_status - def render_si(self, ctx, data): - si_s = base32.b2a_or_none(data.get_storage_index()) + def render_HTML(self, req): + elem = RetrieveStatusElement(self._retrieve_status) + return renderElement(req, elem) + + +class RetrieveStatusElement(Element, RateAndTimeMixin): + + loader = XMLFile(FilePath(__file__).sibling("retrieve-status.xhtml")) + + def __init__(self, retrieve_status): + super(RetrieveStatusElement, self).__init__() + self._retrieve_status = retrieve_status + + @renderer + def started(self, req, tag): + started_s = render_time(self._retrieve_status.get_started()) + return tag(started_s) + + @renderer + def si(self, req, tag): + si_s = base32.b2a_or_none(self._retrieve_status.get_storage_index()) if si_s is None: si_s = "(None)" - return si_s + return tag(si_s) - def render_helper(self, ctx, data): - return {True: "Yes", - False: "No"}[data.using_helper()] + @renderer + def helper(self, req, tag): + return tag({True: "Yes", + False: "No"}[self._retrieve_status.using_helper()]) - def render_current_size(self, ctx, data): - size = data.get_size() + @renderer + def current_size(self, req, tag): + size = self._retrieve_status.get_size() if size is None: size = "(unknown)" - return size + return tag(size) - def render_progress(self, ctx, data): - progress = data.get_progress() + @renderer + def progress(self, req, tag): + progress = self._retrieve_status.get_progress() # TODO: make an ascii-art bar - return "%.1f%%" % (100.0 * progress) + return tag("%.1f%%" % (100.0 * progress)) - def render_status(self, ctx, data): - return data.get_status() + @renderer + def status(self, req, tag): + return tag(self._retrieve_status.get_status()) - def render_encoding(self, ctx, data): - k, n = data.get_encoding() - return ctx.tag["Encoding: %s of %s" % (k, n)] + @renderer + def encoding(self, req, tag): + k, n = self._retrieve_status.get_encoding() + return tag("Encoding: %s of %s" % (k, n)) - def render_problems(self, ctx, data): - problems = data.get_problems() + @renderer + def problems(self, req, tag): + problems = self._retrieve_status.get_problems() if not problems: return "" - l = T.ul() + l = tags.ul() for peerid in sorted(problems.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) - l[T.li["[%s]: %s" % (peerid_s, problems[peerid])]] - return ctx.tag["Server Problems:", l] + l(tags.li("[%s]: %s" % (peerid_s, problems[peerid]))) + return tag("Server Problems:", l) - def _get_rate(self, data, name): - file_size = self.retrieve_status.get_size() - duration = self.retrieve_status.timings.get(name) + def _get_rate(self, name): + file_size = self._retrieve_status.get_size() + duration = self._retrieve_status.timings.get(name) return compute_rate(file_size, duration) - def data_time_total(self, ctx, data): - return self.retrieve_status.timings.get("total") - def data_rate_total(self, ctx, data): - return self._get_rate(data, "total") + @renderer + def time_total(self, req, tag): + return tag(self._retrieve_status.timings.get("total")) - def data_time_fetch(self, ctx, data): - return self.retrieve_status.timings.get("fetch") - def data_rate_fetch(self, ctx, data): - return self._get_rate(data, "fetch") + @renderer + def rate_total(self, req, tag): + return tag(self._get_rate("total")) - def data_time_decode(self, ctx, data): - return self.retrieve_status.timings.get("decode") - def data_rate_decode(self, ctx, data): - return self._get_rate(data, "decode") + @renderer + def time_fetch(self, req, tag): + return tag(self._retrieve_status.timings.get("fetch")) - def data_time_decrypt(self, ctx, data): - return self.retrieve_status.timings.get("decrypt") - def data_rate_decrypt(self, ctx, data): - return self._get_rate(data, "decrypt") + @renderer + def rate_fetch(self, req, tag): + return tag(self._get_rate("fetch")) - def render_server_timings(self, ctx, data): - per_server = self.retrieve_status.timings.get("fetch_per_server") + @renderer + def time_decode(self, req, tag): + return tag(self._retrieve_status.timings.get("decode")) + + @renderer + def rate_decode(self, req, tag): + return tag(self._get_rate("decode")) + + @renderer + def time_decrypt(self, req, tag): + return tag(self._retrieve_status.timings.get("decrypt")) + + @renderer + def rate_decrypt(self, req, tag): + return tag(self._get_rate("decrypt")) + + @renderer + def server_timings(self, req, tag): + per_server = self._retrieve_status.timings.get("fetch_per_server") if not per_server: - return "" - l = T.ul() + return tag("") + l = tags.ul() for server in sorted(per_server.keys(), key=lambda s: s.get_name()): times_s = ", ".join([self.render_time(None, t) for t in per_server[server]]) - l[T.li["[%s]: %s" % (server.get_name(), times_s)]] - return T.li["Per-Server Fetch Response Times: ", l] + l(tags.li("[%s]: %s" % (server.get_name(), times_s))) + return tags.li("Per-Server Fetch Response Times: ", l) class PublishStatusPage(rend.Page, RateAndTimeMixin): From a544e8a55f09cacaf4974d2efd9ecfd1e8e7d4f1 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 7 May 2020 10:55:08 -0400 Subject: [PATCH 0079/1054] Stringify tag parameters --- src/allmydata/web/status.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 60e2a0a70..89168c7ec 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -712,7 +712,7 @@ class RetrieveStatusElement(Element, RateAndTimeMixin): @renderer def current_size(self, req, tag): - size = self._retrieve_status.get_size() + size = str(self._retrieve_status.get_size()) if size is None: size = "(unknown)" return tag(size) @@ -750,35 +750,35 @@ class RetrieveStatusElement(Element, RateAndTimeMixin): @renderer def time_total(self, req, tag): - return tag(self._retrieve_status.timings.get("total")) + return tag(str(self._retrieve_status.timings.get("total"))) @renderer def rate_total(self, req, tag): - return tag(self._get_rate("total")) + return tag(str(self._get_rate("total"))) @renderer def time_fetch(self, req, tag): - return tag(self._retrieve_status.timings.get("fetch")) + return tag(str(self._retrieve_status.timings.get("fetch"))) @renderer def rate_fetch(self, req, tag): - return tag(self._get_rate("fetch")) + return tag(str(self._get_rate("fetch"))) @renderer def time_decode(self, req, tag): - return tag(self._retrieve_status.timings.get("decode")) + return tag(str(self._retrieve_status.timings.get("decode"))) @renderer def rate_decode(self, req, tag): - return tag(self._get_rate("decode")) + return tag(str(self._get_rate("decode"))) @renderer def time_decrypt(self, req, tag): - return tag(self._retrieve_status.timings.get("decrypt")) + return tag(str(self._retrieve_status.timings.get("decrypt"))) @renderer def rate_decrypt(self, req, tag): - return tag(self._get_rate("decrypt")) + return tag(str(self._get_rate("decrypt"))) @renderer def server_timings(self, req, tag): From 07f14a19f81db85aa1a66d2c39a3350036dc8461 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 7 May 2020 11:15:36 -0400 Subject: [PATCH 0080/1054] Drop RateAndTimeMixin from RetrieveStatusElement --- src/allmydata/web/status.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 89168c7ec..3d9175ff1 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -685,7 +685,7 @@ class RetrieveStatusPage(MultiFormatResource): return renderElement(req, elem) -class RetrieveStatusElement(Element, RateAndTimeMixin): +class RetrieveStatusElement(Element): loader = XMLFile(FilePath(__file__).sibling("retrieve-status.xhtml")) @@ -787,7 +787,7 @@ class RetrieveStatusElement(Element, RateAndTimeMixin): return tag("") l = tags.ul() for server in sorted(per_server.keys(), key=lambda s: s.get_name()): - times_s = ", ".join([self.render_time(None, t) + times_s = ", ".join([abbreviate_time(t) for t in per_server[server]]) l(tags.li("[%s]: %s" % (server.get_name(), times_s))) return tags.li("Per-Server Fetch Response Times: ", l) From 3aaa87d2ba58620290f9f448d4efde4ea8cf5a3c Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 7 May 2020 13:15:05 -0400 Subject: [PATCH 0081/1054] Add notes to RetrieveStatusPage --- src/allmydata/web/status.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 3d9175ff1..8a423e69f 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -674,9 +674,14 @@ class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page): return data.get_status() + +# Renders "/status/retrieve-%d". class RetrieveStatusPage(MultiFormatResource): def __init__(self, retrieve_status): + """ + :retrieve_status retrieve.RetrieveStatus: stats provider. + """ super(RetrieveStatusPage, self).__init__() self._retrieve_status = retrieve_status From a39f6d36f4d95b09998fe57cc389f2c6a7da7056 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 7 May 2020 16:40:55 -0400 Subject: [PATCH 0082/1054] Add newsfragment --- newsfragments/3290.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3290.minor diff --git a/newsfragments/3290.minor b/newsfragments/3290.minor new file mode 100644 index 000000000..e69de29bb From 65cd0e8ecd22f4227f30019fb9e7d11e2f7eeee7 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 10:38:15 -0400 Subject: [PATCH 0083/1054] Reformat publish status template page --- src/allmydata/web/publish-status.xhtml | 79 ++++++++++++++------------ 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/src/allmydata/web/publish-status.xhtml b/src/allmydata/web/publish-status.xhtml index 4204de8e2..2c676493f 100644 --- a/src/allmydata/web/publish-status.xhtml +++ b/src/allmydata/web/publish-status.xhtml @@ -1,51 +1,56 @@ + Tahoe-LAFS - Mutable File Publish Status + -

          Mutable File Publish Status

          +

          Mutable File Publish Status

          -
            -
          • Started:
          • -
          • Storage Index:
          • -
          • Helper?:
          • -
          • Current Size:
          • -
          • Progress:
          • -
          • Status:
          • -
          - -

          Publish Results

          -
            -
          • -
          • -
          • -
          • Timings:
          • -
              -
            • Total: - ()
              • -
              • Setup:
              • -
              • Encrypting: - ()
              • -
              • Encoding: - ()
              • -
              • Packing Shares: - () -
                  -
                • RSA Signature:
                • -
              • - -
              • Pushing: - ()
              • +
              • Started:
              • +
              • Storage Index:
              • +
              • Helper?:
              • +
              • Current Size:
              • +
              • Progress:
              • +
              • Status:
              -
            • -
            -
          -
          Return to the Welcome Page
          +

          Publish Results

          - +
            +
          • +
          • +
          • +
          • Timings:
          • +
              +
            • Total: + ()
            • +
                +
              • Setup:
              • +
              • Encrypting: + ()
              • +
              • Encoding: + ()
              • +
              • Packing Shares: + () +
                  +
                • RSA Signature:
                • +
              • + +
              • Pushing: + ()
              • +
              +
            • +
            +
          + +
          Return to the Welcome Page
          + + + + From 89c42100907956c3c6189c3905a133274db84cd3 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 10:40:48 -0400 Subject: [PATCH 0084/1054] Use twisted tags in publish status template page --- src/allmydata/web/publish-status.xhtml | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/allmydata/web/publish-status.xhtml b/src/allmydata/web/publish-status.xhtml index 2c676493f..49f5bb08c 100644 --- a/src/allmydata/web/publish-status.xhtml +++ b/src/allmydata/web/publish-status.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - Mutable File Publish Status @@ -12,40 +12,40 @@

          Mutable File Publish Status

            -
          • Started:
          • -
          • Storage Index:
          • -
          • Helper?:
          • -
          • Current Size:
          • -
          • Progress:
          • -
          • Status:
          • +
          • Started:
          • +
          • Storage Index:
          • +
          • Helper?:
          • +
          • Current Size:
          • +
          • Progress:
          • +
          • Status:

          Publish Results

            -
          • -
          • -
          • +
          • +
          • +
          • Timings:
            • -
            • Total: - ()
            • +
            • Total: + ()
              • -
              • Setup:
              • -
              • Encrypting: - ()
              • -
              • Encoding: - ()
              • -
              • Packing Shares: - () +
              • Setup:
              • +
              • Encrypting: + ()
              • +
              • Encoding: + ()
              • +
              • Packing Shares: + ()
                  -
                • RSA Signature:
                • +
                • RSA Signature:
              • -
              • Pushing: - ()
              • +
              • Pushing: + ()
              -
            • +
          From fdf6449ba8bcff3d1da1283fcd4d12ccf57f4aff Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 11:24:56 -0400 Subject: [PATCH 0085/1054] Use MultiFormatResource to implement PublishStatusPage --- src/allmydata/web/status.py | 190 ++++++++++++++++++++++-------------- 1 file changed, 118 insertions(+), 72 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index bc0d69d77..225f30193 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -759,114 +759,160 @@ class RetrieveStatusPage(rend.Page, RateAndTimeMixin): return T.li["Per-Server Fetch Response Times: ", l] -class PublishStatusPage(rend.Page, RateAndTimeMixin): +class PublishStatusPage(MultiFormatResource): + docFactory = getxmlfile("publish-status.xhtml") - def __init__(self, data): - rend.Page.__init__(self, data) - self.publish_status = data + def __init__(self, publish_status): + super(PublishStatusPage, self).__init__() + self._publish_status = publish_status - def render_started(self, ctx, data): - started_s = render_time(data.get_started()) - return started_s + def render_HTML(self, req): + elem = PublishStatusElement(self._publish_status); + return renderElement(req, elem) - def render_si(self, ctx, data): - si_s = base32.b2a_or_none(data.get_storage_index()) + +class PublishStatusElement(Element, RateAndTimeMixin): + + loader = XMLFile(FilePath(__file__).sibling("publish-status.xhtml")) + + def __init__(self, publish_status): + super(PublishStatusElement, self).__init__() + self._publish_status = publish_status + + @renderer + def started(self, req, tag): + started_s = render_time(self._publish_status.get_started()) + return tag(started_s) + + @renderer + def si(self, req, tag): + si_s = base32.b2a_or_none(self._publish_status.get_storage_index()) if si_s is None: si_s = "(None)" - return si_s + return tag(str(si_s)) - def render_helper(self, ctx, data): - return {True: "Yes", - False: "No"}[data.using_helper()] + @renderer + def helper(self, req, tag): + return tag({True: "Yes", + False: "No"}[self._publish_status.using_helper()]) - def render_current_size(self, ctx, data): - size = data.get_size() + @renderer + def current_size(self, req, tag): + size = self._publish_status.get_size() if size is None: size = "(unknown)" - return size + return tag(str(size)) - def render_progress(self, ctx, data): - progress = data.get_progress() + @renderer + def progress(self, req, tag): + progress = self._publish_status.get_progress() # TODO: make an ascii-art bar - return "%.1f%%" % (100.0 * progress) + return tag("%.1f%%" % (100.0 * progress)) - def render_status(self, ctx, data): - return data.get_status() + @renderer + def status(self, req, tag): + return tag(self._publish_status.get_status()) - def render_encoding(self, ctx, data): - k, n = data.get_encoding() - return ctx.tag["Encoding: %s of %s" % (k, n)] + @renderer + def encoding(self, req, tag): + k, n = self._publish_status.get_encoding() + return tag("Encoding: %s of %s" % (k, n)) - def render_sharemap(self, ctx, data): - servermap = data.get_servermap() + @renderer + def sharemap(self, req, tag): + servermap = self._publish_status.get_servermap() if servermap is None: - return ctx.tag["None"] - l = T.ul() + return tag("None") + l = tags.ul() sharemap = servermap.make_sharemap() for shnum in sorted(sharemap.keys()): - l[T.li["%d -> Placed on " % shnum, - ", ".join(["[%s]" % server.get_name() - for server in sharemap[shnum]])]] - return ctx.tag["Sharemap:", l] + l(tags.li("%d -> Placed on " % shnum, + ", ".join(["[%s]" % server.get_name() + for server in sharemap[shnum]]))) + return tag("Sharemap:", l) - def render_problems(self, ctx, data): - problems = data.get_problems() + @renderer + def problems(self, req, tag): + problems = self._publish_status.get_problems() if not problems: - return "" - l = T.ul() + return tag() + l = tags.ul() # XXX: is this exercised? I don't think PublishStatus.problems is # ever populated for peerid in sorted(problems.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) - l[T.li["[%s]: %s" % (peerid_s, problems[peerid])]] - return ctx.tag["Server Problems:", l] + l(tags.li("[%s]: %s" % (peerid_s, problems[peerid]))) + return tag("Server Problems:", l) - def _get_rate(self, data, name): - file_size = self.publish_status.get_size() - duration = self.publish_status.timings.get(name) - return compute_rate(file_size, duration) + def _get_rate(self, name): + file_size = self._publish_status.get_size() + duration = self._publish_status.timings.get(name) + return str(compute_rate(file_size, duration)) - def data_time_total(self, ctx, data): - return self.publish_status.timings.get("total") - def data_rate_total(self, ctx, data): - return self._get_rate(data, "total") + def _get_time(self, name): + return str(self._publish_status.timings.get(name)) - def data_time_setup(self, ctx, data): - return self.publish_status.timings.get("setup") + @renderer + def time_total(self, req, tag): + return tag(self._get_time("total")) - def data_time_encrypt(self, ctx, data): - return self.publish_status.timings.get("encrypt") - def data_rate_encrypt(self, ctx, data): - return self._get_rate(data, "encrypt") + @renderer + def rate_total(self, req, tag): + return tag(self._get_rate("total")) - def data_time_encode(self, ctx, data): - return self.publish_status.timings.get("encode") - def data_rate_encode(self, ctx, data): - return self._get_rate(data, "encode") + @renderer + def time_setup(self, req, tag): + return tag(self._get_time("setup")) - def data_time_pack(self, ctx, data): - return self.publish_status.timings.get("pack") - def data_rate_pack(self, ctx, data): - return self._get_rate(data, "pack") - def data_time_sign(self, ctx, data): - return self.publish_status.timings.get("sign") + @renderer + def time_encrypt(self, req, tag): + return tag(self._get_time("encrypt")) - def data_time_push(self, ctx, data): - return self.publish_status.timings.get("push") - def data_rate_push(self, ctx, data): - return self._get_rate(data, "push") + @renderer + def rate_encrypt(self, req, tag): + return tag(self._get_rate("encrypt")) - def render_server_timings(self, ctx, data): - per_server = self.publish_status.timings.get("send_per_server") + @renderer + def time_encode(self, req, tag): + return tag(self._get_time("encode")) + + @renderer + def rate_encode(self, req, tag): + return tag(self._get_rate("encode")) + + @renderer + def time_pack(self, req, tag): + return tag(self._get_time("pack")) + + @renderer + def rate_pack(self, req, tag): + return tag(self._get_rate("pack")) + + @renderer + def time_sign(self, req, tag): + return tag(self._get_time("sign")) + + @renderer + def time_push(self, req, tag): + return tag(self._get_time("push")) + + @renderer + def rate_push(self, req, tag): + return self._get_rate("push") + + @renderer + def server_timings(self, req, tag): + per_server = self._publish_status.timings.get("send_per_server") if not per_server: - return "" - l = T.ul() + return tag() + l = tags.ul() for server in sorted(per_server.keys(), key=lambda s: s.get_name()): times_s = ", ".join([self.render_time(None, t) for t in per_server[server]]) - l[T.li["[%s]: %s" % (server.get_name(), times_s)]] - return T.li["Per-Server Response Times: ", l] + l(tags.li("[%s]: %s" % (server.get_name(), times_s))) + return tags.li("Per-Server Response Times: ", l) + class MapupdateStatusPage(rend.Page, RateAndTimeMixin): docFactory = getxmlfile("map-update-status.xhtml") From 0fba615afb192c04e23e47e821abc3e58b57d665 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 11:28:01 -0400 Subject: [PATCH 0086/1054] Remove RateAndTimeMixin from PublishStatusPage --- src/allmydata/web/status.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 225f30193..07585435a 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -772,7 +772,7 @@ class PublishStatusPage(MultiFormatResource): return renderElement(req, elem) -class PublishStatusElement(Element, RateAndTimeMixin): +class PublishStatusElement(Element): loader = XMLFile(FilePath(__file__).sibling("publish-status.xhtml")) @@ -908,7 +908,7 @@ class PublishStatusElement(Element, RateAndTimeMixin): return tag() l = tags.ul() for server in sorted(per_server.keys(), key=lambda s: s.get_name()): - times_s = ", ".join([self.render_time(None, t) + times_s = ", ".join([abbreviate_time(t) for t in per_server[server]]) l(tags.li("[%s]: %s" % (server.get_name(), times_s))) return tags.li("Per-Server Response Times: ", l) From 9e200fc0141615de83f33b681b8644fbe59f7a9d Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 11:34:57 -0400 Subject: [PATCH 0087/1054] Render publish problems only when there are problems --- src/allmydata/web/publish-status.xhtml | 2 +- src/allmydata/web/status.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/publish-status.xhtml b/src/allmydata/web/publish-status.xhtml index 49f5bb08c..1949230c2 100644 --- a/src/allmydata/web/publish-status.xhtml +++ b/src/allmydata/web/publish-status.xhtml @@ -24,7 +24,7 @@
          • -
          • +
          • Timings:
            • diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 07585435a..70edbc4c9 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -843,7 +843,7 @@ class PublishStatusElement(Element): for peerid in sorted(problems.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) l(tags.li("[%s]: %s" % (peerid_s, problems[peerid]))) - return tag("Server Problems:", l) + return tag(tags.li("Server Problems:", l)) def _get_rate(self, name): file_size = self._publish_status.get_size() From a4bc31a13798c66f0ef082355f14d8eefdffcf6d Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 11:42:30 -0400 Subject: [PATCH 0088/1054] Add newsfragment --- newsfragments/3289.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3289.minor diff --git a/newsfragments/3289.minor b/newsfragments/3289.minor new file mode 100644 index 000000000..e69de29bb From 0ae045ea166651c122f92e7c169e501f339845a4 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 12:47:48 -0400 Subject: [PATCH 0089/1054] Add comments to PublishStatusPage --- src/allmydata/web/status.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 70edbc4c9..7e05a2677 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -759,11 +759,13 @@ class RetrieveStatusPage(rend.Page, RateAndTimeMixin): return T.li["Per-Server Fetch Response Times: ", l] +# Renders "status/publish-%n" class PublishStatusPage(MultiFormatResource): - docFactory = getxmlfile("publish-status.xhtml") - def __init__(self, publish_status): + """ + :publish_status mutable.publish.PublishStatus: publish stats provider. + """ super(PublishStatusPage, self).__init__() self._publish_status = publish_status From 3fad9a127c5ec4b1d13f0f3aca53072247a45345 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 12 May 2020 07:40:22 -0400 Subject: [PATCH 0090/1054] Remove web.common.RenderMixin Fixes: ticket:3308 --- newsfragments/3308.minor | 0 src/allmydata/web/common.py | 36 ------------------------------------ 2 files changed, 36 deletions(-) create mode 100644 newsfragments/3308.minor diff --git a/newsfragments/3308.minor b/newsfragments/3308.minor new file mode 100644 index 000000000..e69de29bb diff --git a/src/allmydata/web/common.py b/src/allmydata/web/common.py index e7b9f8ed1..a930fd2b1 100644 --- a/src/allmydata/web/common.py +++ b/src/allmydata/web/common.py @@ -365,42 +365,6 @@ class NeedOperationHandleError(WebError): pass -# XXX should be phased out by the nevow -> twisted.web port (that is, -# this whole class should have no users and can be deleted once the -# port away from nevow is complete) -class RenderMixin(object): - - def renderHTTP(self, ctx): - request = IRequest(ctx) - - # if we were using regular twisted.web Resources (and the regular - # twisted.web.server.Request object) then we could implement - # render_PUT and render_GET. But Nevow's request handler - # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy - # some code from the Resource.render method that Nevow bypasses, to - # do the same thing. - m = getattr(self, 'render_' + request.method, None) - if not m: - raise server.UnsupportedMethod(getattr(self, 'allowedMethods', ())) - return m(ctx) - - def render_OPTIONS(self, ctx): - """ - Handle HTTP OPTIONS request by adding appropriate headers. - - :param ctx: client transaction from which request is extracted - :returns: str (empty) - """ - req = IRequest(ctx) - req.setHeader("server", "Tahoe-LAFS gateway") - methods = ', '.join([m[7:] for m in dir(self) if m.startswith('render_')]) - req.setHeader("allow", methods) - req.setHeader("public", methods) - req.setHeader("compliance", "rfc=2068, rfc=2616") - req.setHeader("content-length", 0) - return "" - - class MultiFormatPage(Page): """ ```MultiFormatPage`` is a ``rend.Page`` that can be rendered in a number From b1d7e9967135f488990c192559e65fd3f38aec3d Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 12 May 2020 18:15:17 -0400 Subject: [PATCH 0091/1054] Try running PyInstaller-generated binary in GitHub Actions --- .github/workflows/ci.yml | 4 ++++ newsfragments/3309.minor | 0 2 files changed, 4 insertions(+) create mode 100644 newsfragments/3309.minor diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d41a78b4..cbb03eb2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -170,3 +170,7 @@ jobs: - name: Run "tox -e pyinstaller" run: tox -e pyinstaller + + # This step is to ensure there are no packaging/import errors. + - name: Test PyInstaller executable + run: dist/Tahoe-LAFS/tahoe --version diff --git a/newsfragments/3309.minor b/newsfragments/3309.minor new file mode 100644 index 000000000..e69de29bb From 86184e9aa9dc475521e54baaed0bca92fd4eb1fe Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 12 May 2020 18:28:42 -0400 Subject: [PATCH 0092/1054] Do not install codecov in GitHub Actions packaging tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbb03eb2b..fce49cdb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,7 @@ jobs: - name: Install Python packages run: | - pip install --upgrade codecov tox setuptools + pip install --upgrade tox setuptools pip list - name: Display tool versions From f3d37e52dbd20ebaf85ac7fb2d73fbd54f5fad30 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Tue, 12 May 2020 18:37:50 -0400 Subject: [PATCH 0093/1054] Upload PyInstaller package as a GitHub Actions artifact --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fce49cdb8..e95686eb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -174,3 +174,9 @@ jobs: # This step is to ensure there are no packaging/import errors. - name: Test PyInstaller executable run: dist/Tahoe-LAFS/tahoe --version + + - name: Upload PyInstaller package + uses: actions/upload-artifact@v2 + with: + name: Tahoe-LAFS-${{ matrix.os }}-Python-${{ matrix.python-version }} + path: dist/Tahoe-LAFS-*-*.* From 4b1ff9546c313fbf1e734ba6bb0350fcf774fdcd Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 12:06:11 -0400 Subject: [PATCH 0094/1054] Do not install setuptools in pyinstaller step This is unnecessary, and likely was the result of copy-pasting. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e95686eb6..2ded8f418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,7 @@ jobs: - name: Install Python packages run: | - pip install --upgrade tox setuptools + pip install --upgrade tox pip list - name: Display tool versions From c18488872ceb00ae7731d4e22bed87fdda54d2ed Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 16:47:51 -0400 Subject: [PATCH 0095/1054] Make comment on MapupdateStatusPage a docstring --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index eaf0f726e..4232a504a 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -869,8 +869,8 @@ class PublishStatusPage(rend.Page, RateAndTimeMixin): return T.li["Per-Server Response Times: ", l] -# Renders "/status/mapupdate-%d" class MapupdateStatusPage(MultiFormatResource): + """Renders "/status/mapupdate-%d.""" def __init__(self, update_status): """ From 8e17d203cf46c6f2a200b6641ed616cf17bb98aa Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 16:59:52 -0400 Subject: [PATCH 0096/1054] Add missing "param:" to docstring --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 4232a504a..5b57aa239 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -874,7 +874,7 @@ class MapupdateStatusPage(MultiFormatResource): def __init__(self, update_status): """ - :update_status servermap.UpdateStatus: server map stats provider. + :param update_status servermap.UpdateStatus: server map stats provider. """ super(MapupdateStatusPage, self).__init__() self._update_status = update_status From 62760c09975566236a847656fdff565854ac1217 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 17:07:09 -0400 Subject: [PATCH 0097/1054] Make RetrieveStatusPage comment a docstring --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 8a423e69f..f925ad961 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -675,8 +675,8 @@ class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page): -# Renders "/status/retrieve-%d". class RetrieveStatusPage(MultiFormatResource): + """Renders /status/retrieve-%d.""" def __init__(self, retrieve_status): """ From 4af2e4b4df300f6d0ad7b2950e9249ae340efb14 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 17:08:26 -0400 Subject: [PATCH 0098/1054] Drop an unbalanced quote --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 5b57aa239..d26079345 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -870,7 +870,7 @@ class PublishStatusPage(rend.Page, RateAndTimeMixin): class MapupdateStatusPage(MultiFormatResource): - """Renders "/status/mapupdate-%d.""" + """Renders /status/mapupdate-%d.""" def __init__(self, update_status): """ From 812f03934ab5f333c812bd6f3887e5795a7e96d9 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 17:09:53 -0400 Subject: [PATCH 0099/1054] Add missing "param:" to docstring --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index f925ad961..3931dc633 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -680,7 +680,7 @@ class RetrieveStatusPage(MultiFormatResource): def __init__(self, retrieve_status): """ - :retrieve_status retrieve.RetrieveStatus: stats provider. + :param retrieve_status retrieve.RetrieveStatus: stats provider. """ super(RetrieveStatusPage, self).__init__() self._retrieve_status = retrieve_status From 5aa0dd4085e07a1a316a69fcaddc0d62e38cf9d4 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 17:12:08 -0400 Subject: [PATCH 0100/1054] Rename a variable for readability Noticed in code review: `l` can be confused with `1` for many typefaces. --- src/allmydata/web/status.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 3931dc633..0dc35b231 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -742,11 +742,11 @@ class RetrieveStatusElement(Element): problems = self._retrieve_status.get_problems() if not problems: return "" - l = tags.ul() + ul = tags.ul() for peerid in sorted(problems.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) - l(tags.li("[%s]: %s" % (peerid_s, problems[peerid]))) - return tag("Server Problems:", l) + ul(tags.li("[%s]: %s" % (peerid_s, problems[peerid]))) + return tag("Server Problems:", ul) def _get_rate(self, name): file_size = self._retrieve_status.get_size() From 2e0e210c14fd533bf5fa477e3a385b5876a53c5a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 17:43:55 -0400 Subject: [PATCH 0101/1054] Make PublishStatusPage comment a docstring --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 7e05a2677..203b73fd6 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -759,8 +759,8 @@ class RetrieveStatusPage(rend.Page, RateAndTimeMixin): return T.li["Per-Server Fetch Response Times: ", l] -# Renders "status/publish-%n" class PublishStatusPage(MultiFormatResource): + """Renders status/publish-%d.""" def __init__(self, publish_status): """ From ebe80221d59208620c4c2ddcf397a89069ed2a3f Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 17:44:50 -0400 Subject: [PATCH 0102/1054] Add missing "param:" to init method docstring --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 203b73fd6..68c5d5341 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -764,7 +764,7 @@ class PublishStatusPage(MultiFormatResource): def __init__(self, publish_status): """ - :publish_status mutable.publish.PublishStatus: publish stats provider. + :param publish_status mutable.publish.PublishStatus: stats provider. """ super(PublishStatusPage, self).__init__() self._publish_status = publish_status From 9d5d3389938159cf1c43fb60cf90bc2569ab0206 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 19:04:10 -0400 Subject: [PATCH 0103/1054] Correct docstring to ":param " format --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 0dc35b231..d62edb3d2 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -680,7 +680,7 @@ class RetrieveStatusPage(MultiFormatResource): def __init__(self, retrieve_status): """ - :param retrieve_status retrieve.RetrieveStatus: stats provider. + :param retrieve.RetrieveStatus retrieve_status: stats provider. """ super(RetrieveStatusPage, self).__init__() self._retrieve_status = retrieve_status From 79a44eac33ded8088ec58840fb8e87a54dfc7c5f Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Wed, 13 May 2020 19:29:41 -0400 Subject: [PATCH 0104/1054] Correct docstring to ":param " format --- src/allmydata/web/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 68c5d5341..0217978d3 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -764,7 +764,7 @@ class PublishStatusPage(MultiFormatResource): def __init__(self, publish_status): """ - :param publish_status mutable.publish.PublishStatus: stats provider. + :param mutable.publish.PublishStatus publish_status: stats provider. """ super(PublishStatusPage, self).__init__() self._publish_status = publish_status From a203f9c8d837fb569ac3ccf7d1cd3ea70142a10e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 13:14:10 -0400 Subject: [PATCH 0105/1054] Reformat download status page template --- src/allmydata/web/download-status.xhtml | 89 +++++++++++++------------ 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/allmydata/web/download-status.xhtml b/src/allmydata/web/download-status.xhtml index e5794ca79..e47f1f1f8 100644 --- a/src/allmydata/web/download-status.xhtml +++ b/src/allmydata/web/download-status.xhtml @@ -1,58 +1,63 @@ + Tahoe-LAFS - File Download Status + -

              File Download Status

              +

              File Download Status

              -
                -
              • Started:
              • -
              • Storage Index:
              • -
              • Helper?:
              • -
              • Total Size:
              • -
              • Progress:
              • -
              • Status:
              • -
              - -
              - -
              -

              Download Results

              -
                -
              • -
              • Servermap:
              • -
              • -
              • Timings:
                • -
                • File Size: bytes
                • -
                • Total: - ()
                • -
                    -
                  • Peer Selection:
                  • -
                  • UEB Fetch:
                  • -
                  • Hashtree Fetch:
                  • -
                  • Segment Fetch: - ()
                  • -
                      -
                    • Cumulative Fetching: - ()
                    • -
                    • Cumulative Decoding: - ()
                    • -
                    • Cumulative Decrypting: - ()
                    • -
                    -
                  • Paused by client:
                  • -
                  -
                • +
                • Started:
                • +
                • Storage Index:
                • +
                • Helper?:
                • +
                • Total Size:
                • +
                • Progress:
                • +
                • Status:
                -
              -
              -
              Return to the Welcome Page
              +
              + +
              + +

              Download Results

              + +
                +
              • +
              • Servermap:
              • +
              • +
              • Timings:
              • +
                  +
                • File Size: bytes
                • +
                • Total: + ()
                • +
                    +
                  • Peer Selection:
                  • +
                  • UEB Fetch:
                  • +
                  • Hashtree Fetch:
                  • +
                  • Segment Fetch: + ()
                  • +
                      +
                    • Cumulative Fetching: + ()
                    • +
                    • Cumulative Decoding: + ()
                    • +
                    • Cumulative Decrypting: + ()
                    • +
                    +
                  • Paused by client:
                  • +
                  +
                • +
                +
              +
              + +
              Return to the Welcome Page
              + From 8a632b666826651a749699883ece477c9e2e025e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 13:20:52 -0400 Subject: [PATCH 0106/1054] Use twisted tags in download status page template --- src/allmydata/web/download-status.xhtml | 56 ++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/allmydata/web/download-status.xhtml b/src/allmydata/web/download-status.xhtml index e47f1f1f8..93bd08408 100644 --- a/src/allmydata/web/download-status.xhtml +++ b/src/allmydata/web/download-status.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - File Download Status @@ -12,46 +12,46 @@

              File Download Status

                -
              • Started:
              • -
              • Storage Index:
              • -
              • Helper?:
              • -
              • Total Size:
              • -
              • Progress:
              • -
              • Status:
              • +
              • Started:
              • +
              • Storage Index:
              • +
              • Helper?:
              • +
              • Total Size:
              • +
              • Progress:
              • +
              • Status:
              -
              +
              -
              +

              Download Results

                -
              • -
              • Servermap:
              • -
              • +
              • +
              • Servermap:
              • +
              • Timings:
                • -
                • File Size: bytes
                • -
                • Total: - ()
                • +
                • File Size: bytes
                • +
                • Total: + ()
                  • -
                  • Peer Selection:
                  • -
                  • UEB Fetch:
                  • -
                  • Hashtree Fetch:
                  • -
                  • Segment Fetch: - ()
                  • +
                  • Peer Selection:
                  • +
                  • UEB Fetch:
                  • +
                  • Hashtree Fetch:
                  • +
                  • Segment Fetch: + ()
                    • -
                    • Cumulative Fetching: - ()
                    • -
                    • Cumulative Decoding: - ()
                    • -
                    • Cumulative Decrypting: - ()
                    • +
                    • Cumulative Fetching: + ()
                    • +
                    • Cumulative Decoding: + ()
                    • +
                    • Cumulative Decrypting: + ()
                    -
                  • Paused by client:
                  • +
                  • Paused by client:
                  -
                • +
              From c14c4371523d371501e88f0d0103235729f7613e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 13:34:18 -0400 Subject: [PATCH 0107/1054] Make DownloadStatusPage a MultiFormatResource --- src/allmydata/web/status.py | 65 ++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index b20aa679e..85417fcee 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -489,13 +489,25 @@ class _EventJson(Resource, object): return json.dumps(data, indent=1) + "\n" -class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page): - docFactory = getxmlfile("download-status.xhtml") +class DownloadStatusPage(MultiFormatResource): - def __init__(self, data): - rend.Page.__init__(self, data) - self.download_status = data - self.putChild("event_json", _EventJson(self.download_status)) + def __init__(self, download_status): + super(DownloadStatusPage, self).__init__() + self._download_status = download_status + self.putChild("event_json", _EventJson(self._download_status)) + + def render_HTML(self, req): + elem = DownloadStatusElement(self._download_status) + return renderElement(req, elem) + + +class DownloadStatusElement(Element, DownloadResultsRendererMixin): + + loader = XMLFile(FilePath(__file__).sibling("download-status.xhtml")) + + def __init__(self, download_status): + super(DownloadStatusElement, self).__init__() + self._download_status = download_status def download_results(self): return defer.maybeDeferred(self.download_status.get_results) @@ -645,33 +657,40 @@ class DownloadStatusPage(DownloadResultsRendererMixin, rend.Page): d.addCallback(_got_results) return d - def render_started(self, ctx, data): - started_s = render_time(data.get_started()) - return started_s + " (%s)" % data.get_started() + @renderer + def started(self, req, tag): + started_s = render_time(self._download_status.get_started()) + return tag(started_s + " (%s)" % self._download_status.get_started()) - def render_si(self, ctx, data): - si_s = base32.b2a_or_none(data.get_storage_index()) + @renderer + def si(self, req, tag): + si_s = base32.b2a_or_none(self._download_status.get_storage_index()) if si_s is None: si_s = "(None)" - return si_s + return tag(si_s) - def render_helper(self, ctx, data): - return {True: "Yes", - False: "No"}[data.using_helper()] + @renderer + def helper(self, req, tag): + return tag({True: "Yes", + False: "No"}[self._download_status.using_helper()]) - def render_total_size(self, ctx, data): - size = data.get_size() + @renderer + def total_size(self, req, tag): + size = self._download_status.get_size() if size is None: return "(unknown)" - return size + return tag(str(size)) - def render_progress(self, ctx, data): - progress = data.get_progress() + @renderer + def progress(self, req, tag): + progress = self._download_status.get_progress() # TODO: make an ascii-art bar - return "%.1f%%" % (100.0 * progress) + return tag("%.1f%%" % (100.0 * progress)) + + @renderer + def status(self, req, tag): + return tag(self._download_status.get_status()) - def render_status(self, ctx, data): - return data.get_status() From c4b63ada55d7f1a50ffb2941c677ed2f43213eff Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 14:24:26 -0400 Subject: [PATCH 0108/1054] Update events tables renderer --- src/allmydata/web/status.py | 172 +++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 63 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 85417fcee..bdc5874f5 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -512,14 +512,15 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): def download_results(self): return defer.maybeDeferred(self.download_status.get_results) - def relative_time(self, t): + def _relative_time(self, t): if t is None: return t - if self.download_status.first_timestamp is not None: - return t - self.download_status.first_timestamp + if self._download_status.first_timestamp is not None: + return t - self._download_status.first_timestamp return t - def short_relative_time(self, t): - t = self.relative_time(t) + + def _short_relative_time(self, t): + t = self._relative_time(t) if t is None: return "" return "+%.6fs" % t @@ -532,19 +533,27 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): time_s = self.render_time(None, seconds) if seconds != 0: rate = self.render_rate(None, 1.0 * bytes / seconds) - return T.span(title=rate)[time_s] - return T.span[time_s] + return tags.span(time_s, title=rate) + return tags.span(time_s) - def render_events(self, ctx, data): - if not self.download_status.storage_index: + @renderer + def events(self, req, tag): + if not self._download_status.storage_index: return - srt = self.short_relative_time - l = T.div() - t = T.table(align="left", class_="status-download-events") - t[T.tr[T.th["serverid"], T.th["sent"], T.th["received"], - T.th["shnums"], T.th["RTT"]]] - for d_ev in self.download_status.dyhb_requests: + srt = self._short_relative_time + + l = tags.div() + + t = tags.table(align="left", class_="status-download-events") + + t(tags.tr(tags.th("serverid"), + tags.th("sent"), + tags.th("received"), + tags.th("shnums"), + tags.th("RTT"))) + + for d_ev in self._download_status.dyhb_requests: server = d_ev["server"] sent = d_ev["start_time"] shnums = d_ev["response_shnums"] @@ -554,20 +563,31 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): rtt = received - sent if not shnums: shnums = ["-"] - t[T.tr(style="background: %s" % _color(server))[ - [T.td[server.get_name()], T.td[srt(sent)], T.td[srt(received)], - T.td[",".join([str(shnum) for shnum in shnums])], - T.td[self.render_time(None, rtt)], - ]]] - l[T.h2["DYHB Requests:"], t] - l[T.br(clear="all")] + t(tags.tr(style="background: %s" % _color(server))( + (tags.td(server.get_name()), + tags.td(srt(sent)), + tags.td(srt(received)), + tags.td(",".join([str(shnum) for shnum in shnums])), + tags.td(self.render_time(None, rtt)), + ))) - t = T.table(align="left",class_="status-download-events") - t[T.tr[T.th["range"], T.th["start"], T.th["finish"], T.th["got"], - T.th["time"], T.th["decrypttime"], T.th["pausedtime"], - T.th["speed"]]] - for r_ev in self.download_status.read_events: + l(tags.h2("DYHB Requests:"), t) + l(tags.br(clear="all")) + + t = tags.table(align="left",class_="status-download-events") + + t(tags.tr(( + tags.th("range"), + tags.th("start"), + tags.th("finish"), + tags.th("got"), + tags.th("time"), + tags.th("decrypttime"), + tags.th("pausedtime"), + tags.th("speed")))) + + for r_ev in self._download_status.read_events: start = r_ev["start"] length = r_ev["length"] bytes = r_ev["bytes_returned"] @@ -581,21 +601,33 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): rtt = self.render_time(None, rtt) paused = self.render_time(None, r_ev["paused_time"]) - t[T.tr[T.td["[%d:+%d]" % (start, length)], - T.td[srt(r_ev["start_time"])], T.td[srt(r_ev["finish_time"])], - T.td[bytes], T.td[rtt], - T.td[decrypt_time], T.td[paused], - T.td[speed], - ]] + t(tags.tr( + tags.td("[%d:+%d]" % (start, length)), + tags.td(srt(r_ev["start_time"])), + tags.td(srt(r_ev["finish_time"])), + tags.td(str(bytes)), + tags.td(rtt), + tags.td(decrypt_time), + tags.td(paused), + tags.td(speed), + )) - l[T.h2["Read Events:"], t] - l[T.br(clear="all")] + l(tags.h2("Read Events:"), t) + l(tags.br(clear="all")) - t = T.table(align="left",class_="status-download-events") - t[T.tr[T.th["segnum"], T.th["start"], T.th["active"], T.th["finish"], - T.th["range"], - T.th["decodetime"], T.th["segtime"], T.th["speed"]]] - for s_ev in self.download_status.segment_events: + t = tags.table(align="left",class_="status-download-events") + + t(tags.tr( + tags.th("segnum"), + tags.th("start"), + tags.th("active"), + tags.th("finish"), + tags.th("range"), + tags.th("decodetime"), + tags.th("segtime"), + tags.th("speed"))) + + for s_ev in self._download_status.segment_events: range_s = "-" segtime_s = "-" speed = "-" @@ -615,36 +647,50 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): # not finished yet pass - t[T.tr[T.td["seg%d" % s_ev["segment_number"]], - T.td[srt(s_ev["start_time"])], - T.td[srt(s_ev["active_time"])], - T.td[srt(s_ev["finish_time"])], - T.td[range_s], - T.td[decode_time], - T.td[segtime_s], T.td[speed]]] + t(tags.tr( + tags.td("seg%d" % s_ev["segment_number"]), + tags.td(srt(s_ev["start_time"])), + tags.td(srt(s_ev["active_time"])), + tags.td(srt(s_ev["finish_time"])), + tags.td(range_s), + tags.td(decode_time), + tags.td(segtime_s), + tags.td(speed))) - l[T.h2["Segment Events:"], t] - l[T.br(clear="all")] - t = T.table(align="left",class_="status-download-events") - t[T.tr[T.th["serverid"], T.th["shnum"], T.th["range"], - T.th["txtime"], T.th["rxtime"], - T.th["received"], T.th["RTT"]]] - for r_ev in self.download_status.block_requests: + l(tags.h2("Segment Events:"), t) + + l(tags.br(clear="all")) + + t = tags.table(align="left",class_="status-download-events") + + t(tags.tr( + tags.th("serverid"), + tags.th("shnum"), + tags.th("range"), + tags.th("txtime"), + tags.th("rxtime"), + tags.th("received"), + tags.th("RTT"))) + + for r_ev in self._download_status.block_requests: server = r_ev["server"] rtt = None if r_ev["finish_time"] is not None: rtt = r_ev["finish_time"] - r_ev["start_time"] color = _color(server) - t[T.tr(style="background: %s" % color)[ - T.td[server.get_name()], T.td[r_ev["shnum"]], - T.td["[%d:+%d]" % (r_ev["start"], r_ev["length"])], - T.td[srt(r_ev["start_time"])], T.td[srt(r_ev["finish_time"])], - T.td[r_ev["response_length"] or ""], - T.td[self.render_time(None, rtt)], - ]] + t(tags.tr(style="background: %s" % color) + ( + tags.td(server.get_name()), + tags.td(str(r_ev["shnum"])), + tags.td("[%d:+%d]" % (r_ev["start"], r_ev["length"])), + tags.td(srt(r_ev["start_time"])), + tags.td(srt(r_ev["finish_time"])), + tags.td(str(r_ev["response_length"]) or ""), + tags.td(self.render_time(None, rtt)), + )) - l[T.h2["Requests:"], t] - l[T.br(clear="all")] + l(tags.h2("Requests:"), t) + l(tags.br(clear="all")) return l From c5b2073bf04aad921f48fd49bfde78d58c4b090b Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 15:08:44 -0400 Subject: [PATCH 0109/1054] Avoid use of RateAndTimeMixin methods in DownloadStatusElement --- src/allmydata/web/status.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index bdc5874f5..2afe18fef 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -510,7 +510,7 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): self._download_status = download_status def download_results(self): - return defer.maybeDeferred(self.download_status.get_results) + return defer.maybeDeferred(self._download_status.get_results) def _relative_time(self, t): if t is None: @@ -530,9 +530,9 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): return T.a(href=url.URL.fromContext(ctx).child("timeline"))["timeline"] def _rate_and_time(self, bytes, seconds): - time_s = self.render_time(None, seconds) + time_s = abbreviate_time(seconds) if seconds != 0: - rate = self.render_rate(None, 1.0 * bytes / seconds) + rate = abbreviate_rate(1.0 * bytes / seconds) return tags.span(time_s, title=rate) return tags.span(time_s) @@ -569,7 +569,7 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): tags.td(srt(sent)), tags.td(srt(received)), tags.td(",".join([str(shnum) for shnum in shnums])), - tags.td(self.render_time(None, rtt)), + tags.td(abbreviate_time(rtt)), ))) l(tags.h2("DYHB Requests:"), t) @@ -597,9 +597,9 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): speed, rtt = "","" if r_ev["finish_time"] is not None: rtt = r_ev["finish_time"] - r_ev["start_time"] - r_ev["paused_time"] - speed = self.render_rate(None, compute_rate(bytes, rtt)) - rtt = self.render_time(None, rtt) - paused = self.render_time(None, r_ev["paused_time"]) + speed = abbreviate_rate(compute_rate(bytes, rtt)) + rtt = abbreviate_time(rtt) + paused = abbreviate_time(r_ev["paused_time"]) t(tags.tr( tags.td("[%d:+%d]" % (start, length)), @@ -635,10 +635,10 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): if s_ev["finish_time"] is not None: if s_ev["success"]: segtime = s_ev["finish_time"] - s_ev["active_time"] - segtime_s = self.render_time(None, segtime) + segtime_s = abbreviate_time(segtime) seglen = s_ev["segment_length"] range_s = "[%d:+%d]" % (s_ev["segment_start"], seglen) - speed = self.render_rate(None, compute_rate(seglen, segtime)) + speed = abbreviate_rate(compute_rate(seglen, segtime)) decode_time = self._rate_and_time(seglen, s_ev["decode_time"]) else: # error @@ -686,7 +686,7 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): tags.td(srt(r_ev["start_time"])), tags.td(srt(r_ev["finish_time"])), tags.td(str(r_ev["response_length"]) or ""), - tags.td(self.render_time(None, rtt)), + tags.td(abbreviate_time(rtt)), )) l(tags.h2("Requests:"), t) From e0241655e5b24d5db4228b173a1950295dc1e574 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 11 May 2020 15:09:16 -0400 Subject: [PATCH 0110/1054] Update results renderer --- src/allmydata/web/status.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 2afe18fef..1e6c05488 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -694,11 +694,12 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): return l - def render_results(self, ctx, data): + @renderer + def results(self, req, tag): d = self.download_results() def _got_results(results): if results: - return ctx.tag + return tag return "" d.addCallback(_got_results) return d From 7e02502268a4083230e59389a041e1cd022ca2a0 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 11:41:54 -0400 Subject: [PATCH 0111/1054] Add comments to DownloadStatusPage --- src/allmydata/web/status.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 1e6c05488..9be4ee09a 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -490,8 +490,12 @@ class _EventJson(Resource, object): class DownloadStatusPage(MultiFormatResource): + """Renders /status/down-%d.""" def __init__(self, download_status): + """ + :param IDownloadStatus download_status: stats provider + """ super(DownloadStatusPage, self).__init__() self._download_status = download_status self.putChild("event_json", _EventJson(self._download_status)) From b02cfa5bbe8ff3a07156552464028f0db1a2b863 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 11:46:20 -0400 Subject: [PATCH 0112/1054] Use more descriptive variable names --- src/allmydata/web/status.py | 71 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 9be4ee09a..c4ad01dfe 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -547,15 +547,15 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): srt = self._short_relative_time - l = tags.div() + evtag = tags.div() - t = tags.table(align="left", class_="status-download-events") + dyhbtag = tags.table(align="left", class_="status-download-events") - t(tags.tr(tags.th("serverid"), - tags.th("sent"), - tags.th("received"), - tags.th("shnums"), - tags.th("RTT"))) + dyhbtag(tags.tr(tags.th("serverid"), + tags.th("sent"), + tags.th("received"), + tags.th("shnums"), + tags.th("RTT"))) for d_ev in self._download_status.dyhb_requests: server = d_ev["server"] @@ -568,7 +568,7 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): if not shnums: shnums = ["-"] - t(tags.tr(style="background: %s" % _color(server))( + dyhbtag(tags.tr(style="background: %s" % _color(server))( (tags.td(server.get_name()), tags.td(srt(sent)), tags.td(srt(received)), @@ -576,12 +576,12 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): tags.td(abbreviate_time(rtt)), ))) - l(tags.h2("DYHB Requests:"), t) - l(tags.br(clear="all")) + evtag(tags.h2("DYHB Requests:"), dyhbtag) + evtag(tags.br(clear="all")) - t = tags.table(align="left",class_="status-download-events") + readtag = tags.table(align="left",class_="status-download-events") - t(tags.tr(( + readtag(tags.tr(( tags.th("range"), tags.th("start"), tags.th("finish"), @@ -605,7 +605,7 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): rtt = abbreviate_time(rtt) paused = abbreviate_time(r_ev["paused_time"]) - t(tags.tr( + readtag(tags.tr( tags.td("[%d:+%d]" % (start, length)), tags.td(srt(r_ev["start_time"])), tags.td(srt(r_ev["finish_time"])), @@ -616,12 +616,12 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): tags.td(speed), )) - l(tags.h2("Read Events:"), t) - l(tags.br(clear="all")) + evtag(tags.h2("Read Events:"), readtag) + evtag(tags.br(clear="all")) - t = tags.table(align="left",class_="status-download-events") + segtag = tags.table(align="left",class_="status-download-events") - t(tags.tr( + segtag(tags.tr( tags.th("segnum"), tags.th("start"), tags.th("active"), @@ -651,7 +651,7 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): # not finished yet pass - t(tags.tr( + segtag(tags.tr( tags.td("seg%d" % s_ev["segment_number"]), tags.td(srt(s_ev["start_time"])), tags.td(srt(s_ev["active_time"])), @@ -661,13 +661,12 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): tags.td(segtime_s), tags.td(speed))) - l(tags.h2("Segment Events:"), t) + evtag(tags.h2("Segment Events:"), segtag) + evtag(tags.br(clear="all")) - l(tags.br(clear="all")) + reqtab = tags.table(align="left",class_="status-download-events") - t = tags.table(align="left",class_="status-download-events") - - t(tags.tr( + reqtab(tags.tr( tags.th("serverid"), tags.th("shnum"), tags.th("range"), @@ -682,21 +681,21 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): if r_ev["finish_time"] is not None: rtt = r_ev["finish_time"] - r_ev["start_time"] color = _color(server) - t(tags.tr(style="background: %s" % color) - ( - tags.td(server.get_name()), - tags.td(str(r_ev["shnum"])), - tags.td("[%d:+%d]" % (r_ev["start"], r_ev["length"])), - tags.td(srt(r_ev["start_time"])), - tags.td(srt(r_ev["finish_time"])), - tags.td(str(r_ev["response_length"]) or ""), - tags.td(abbreviate_time(rtt)), - )) + reqtab(tags.tr(style="background: %s" % color) + ( + tags.td(server.get_name()), + tags.td(str(r_ev["shnum"])), + tags.td("[%d:+%d]" % (r_ev["start"], r_ev["length"])), + tags.td(srt(r_ev["start_time"])), + tags.td(srt(r_ev["finish_time"])), + tags.td(str(r_ev["response_length"]) or ""), + tags.td(abbreviate_time(rtt)), + )) - l(tags.h2("Requests:"), t) - l(tags.br(clear="all")) + evtag(tags.h2("Requests:"), reqtab) + evtag(tags.br(clear="all")) - return l + return evtag @renderer def results(self, req, tag): From f3edc8a48cc2647e05fce7e063a06937b8f96c35 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 12:50:17 -0400 Subject: [PATCH 0113/1054] Add a note about rendering download events tables --- src/allmydata/web/status.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index c4ad01dfe..209a01317 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -540,6 +540,9 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): return tags.span(time_s, title=rate) return tags.span(time_s) + # XXX: This method is a candidate for refactoring. It renders + # four tables from this function. Layout part of those tables + # could be moved to download-status.xhtml. @renderer def events(self, req, tag): if not self._download_status.storage_index: From 1f1f3b5f6100b94122ce01f5bcb669a4b42f7b33 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 12:59:06 -0400 Subject: [PATCH 0114/1054] Remove unused render_timeline_link() As it turns out, nothing is using this method. --- src/allmydata/web/status.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 209a01317..b6d8b18e4 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -529,10 +529,6 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): return "" return "+%.6fs" % t - def render_timeline_link(self, ctx, data): - from nevow import url - return T.a(href=url.URL.fromContext(ctx).child("timeline"))["timeline"] - def _rate_and_time(self, bytes, seconds): time_s = abbreviate_time(seconds) if seconds != 0: From 1fa77d89834ac64035f26a41c9e5561f49cbb89e Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 14:07:19 -0400 Subject: [PATCH 0115/1054] Avoid use of DownloadResultsRendererMixin --- src/allmydata/web/status.py | 263 +++++++++++++++++++----------------- 1 file changed, 139 insertions(+), 124 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index b6d8b18e4..0628773da 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -217,129 +217,6 @@ class UploadStatusPage(UploadResultsRendererMixin, rend.Page): def render_status(self, ctx, data): return data.get_status() -class DownloadResultsRendererMixin(RateAndTimeMixin): - # this requires a method named 'download_results' - - def render_servermap(self, ctx, data): - d = self.download_results() - d.addCallback(lambda res: res.servermap) - def _render(servermap): - if servermap is None: - return "None" - l = T.ul() - for peerid in sorted(servermap.keys()): - peerid_s = idlib.shortnodeid_b2a(peerid) - shares_s = ",".join(["#%d" % shnum - for shnum in servermap[peerid]]) - l[T.li["[%s] has share%s: %s" % (peerid_s, - plural(servermap[peerid]), - shares_s)]] - return l - d.addCallback(_render) - return d - - def render_servers_used(self, ctx, data): - d = self.download_results() - d.addCallback(lambda res: res.servers_used) - def _got(servers_used): - if not servers_used: - return "" - peerids_s = ", ".join(["[%s]" % idlib.shortnodeid_b2a(peerid) - for peerid in servers_used]) - return T.li["Servers Used: ", peerids_s] - d.addCallback(_got) - return d - - def render_problems(self, ctx, data): - d = self.download_results() - d.addCallback(lambda res: res.server_problems) - def _got(server_problems): - if not server_problems: - return "" - l = T.ul() - for peerid in sorted(server_problems.keys()): - peerid_s = idlib.shortnodeid_b2a(peerid) - l[T.li["[%s]: %s" % (peerid_s, server_problems[peerid])]] - return T.li["Server Problems:", l] - d.addCallback(_got) - return d - - def data_file_size(self, ctx, data): - d = self.download_results() - d.addCallback(lambda res: res.file_size) - return d - - def _get_time(self, name): - d = self.download_results() - d.addCallback(lambda res: res.timings.get(name)) - return d - - def data_time_total(self, ctx, data): - return self._get_time("total") - - def data_time_peer_selection(self, ctx, data): - return self._get_time("peer_selection") - - def data_time_uri_extension(self, ctx, data): - return self._get_time("uri_extension") - - def data_time_hashtrees(self, ctx, data): - return self._get_time("hashtrees") - - def data_time_segments(self, ctx, data): - return self._get_time("segments") - - def data_time_cumulative_fetch(self, ctx, data): - return self._get_time("cumulative_fetch") - - def data_time_cumulative_decode(self, ctx, data): - return self._get_time("cumulative_decode") - - def data_time_cumulative_decrypt(self, ctx, data): - return self._get_time("cumulative_decrypt") - - def data_time_paused(self, ctx, data): - return self._get_time("paused") - - def _get_rate(self, name): - d = self.download_results() - def _convert(r): - file_size = r.file_size - duration = r.timings.get(name) - return compute_rate(file_size, duration) - d.addCallback(_convert) - return d - - def data_rate_total(self, ctx, data): - return self._get_rate("total") - - def data_rate_segments(self, ctx, data): - return self._get_rate("segments") - - def data_rate_fetch(self, ctx, data): - return self._get_rate("cumulative_fetch") - - def data_rate_decode(self, ctx, data): - return self._get_rate("cumulative_decode") - - def data_rate_decrypt(self, ctx, data): - return self._get_rate("cumulative_decrypt") - - def render_server_timings(self, ctx, data): - d = self.download_results() - d.addCallback(lambda res: res.timings.get("fetch_per_server")) - def _render(per_server): - if per_server is None: - return "" - l = T.ul() - for peerid in sorted(per_server.keys()): - peerid_s = idlib.shortnodeid_b2a(peerid) - times_s = ", ".join([self.render_time(None, t) - for t in per_server[peerid]]) - l[T.li["[%s]: %s" % (peerid_s, times_s)]] - return T.li["Per-Server Segment Fetch Response Times: ", l] - d.addCallback(_render) - return d def _find_overlap(events, start_key, end_key): """ @@ -505,7 +382,7 @@ class DownloadStatusPage(MultiFormatResource): return renderElement(req, elem) -class DownloadStatusElement(Element, DownloadResultsRendererMixin): +class DownloadStatusElement(Element): loader = XMLFile(FilePath(__file__).sibling("download-status.xhtml")) @@ -740,7 +617,145 @@ class DownloadStatusElement(Element, DownloadResultsRendererMixin): def status(self, req, tag): return tag(self._download_status.get_status()) + @renderer + def servers_used(self, req, tag): + d = self.download_results() + d.addCallback(lambda res: res.servers_used) + def _got(servers_used): + if not servers_used: + return "" + peerids_s = ", ".join(["[%s]" % idlib.shortnodeid_b2a(peerid) + for peerid in servers_used]) + return tags.li("Servers Used: ", peerids_s) + d.addCallback(_got) + return d + @renderer + def servermap(self, req, tag): + d = self.download_results() + d.addCallback(lambda res: res.servermap) + def _render(servermap): + if servermap is None: + return tag("None") + ul = tags.ul() + for peerid in sorted(servermap.keys()): + peerid_s = idlib.shortnodeid_b2a(peerid) + shares_s = ",".join(["#%d" % shnum + for shnum in servermap[peerid]]) + ul(tags.li("[%s] has share%s: %s" % (peerid_s, + plural(servermap[peerid]), + shares_s))) + return ul + d.addCallback(_render) + return d + + @renderer + def problems(self, req, tag): + d = self.download_results() + d.addCallback(lambda res: res.server_problems) + def _got(server_problems): + if not server_problems: + return tag("") + ul = T.ul() + for peerid in sorted(server_problems.keys()): + peerid_s = idlib.shortnodeid_b2a(peerid) + ul(tags.li("[%s]: %s" % (peerid_s, server_problems[peerid]))) + return tags.li("Server Problems:", ul) + d.addCallback(_got) + return d + + @renderer + def file_size(self, req, tag): + d = self.download_results() + d.addCallback(lambda res: str(res.file_size)) + return d + + def _get_time(self, name): + d = self.download_results() + d.addCallback(lambda res: res.timings.get(name)) + return d + + @renderer + def time_total(self, req, tag): + return self._get_time("total") + + @renderer + def time_peer_selection(self, req, tag): + return self._get_time("peer_selection") + + @renderer + def time_uri_extension(self, req, tag): + return self._get_time("uri_extension") + + @renderer + def time_hashtrees(self, req, tag): + return self._get_time("hashtrees") + + @renderer + def time_segments(self, req, tag): + return self._get_time("segments") + + @renderer + def time_cumulative_fetch(self, req, tag): + return self._get_time("cumulative_fetch") + + @renderer + def time_cumulative_decode(self, req, tag): + return self._get_time("cumulative_decode") + + @renderer + def time_cumulative_decrypt(self, req, tag): + return self._get_time("cumulative_decrypt") + + @renderer + def time_paused(self, req, tag): + return self._get_time("paused") + + def _get_rate(self, name): + d = self.download_results() + def _convert(r): + file_size = r.file_size + duration = r.timings.get(name) + return compute_rate(file_size, duration) + d.addCallback(_convert) + return d + + @renderer + def rate_total(self, req, tag): + return self._get_rate("total") + + @renderer + def rate_segments(self, req, tag): + return self._get_rate("segments") + + @renderer + def rate_fetch(self, req, tag): + return self._get_rate("cumulative_fetch") + + @renderer + def rate_decode(self, req, tag): + return self._get_rate("cumulative_decode") + + @renderer + def rate_decrypt(self, req, tag): + return self._get_rate("cumulative_decrypt") + + @renderer + def server_timings(self, req, tag): + d = self.download_results() + d.addCallback(lambda res: res.timings.get("fetch_per_server")) + def _render(per_server): + if per_server is None: + return "" + ul = tags.ul() + for peerid in sorted(per_server.keys()): + peerid_s = idlib.shortnodeid_b2a(peerid) + times_s = ", ".join([self.render_time(None, t) + for t in per_server[peerid]]) + ul(tags.li("[%s]: %s" % (peerid_s, times_s))) + return tags.li("Per-Server Segment Fetch Response Times: ", ul) + d.addCallback(_render) + return d class RetrieveStatusPage(MultiFormatResource): From 68df408229431acaf0b2fdd67d76809368afd820 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 15:20:59 -0400 Subject: [PATCH 0116/1054] Add a note about unimplemented DownloadStatus method --- src/allmydata/web/status.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 0628773da..83e588bd9 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -390,6 +390,12 @@ class DownloadStatusElement(Element): super(DownloadStatusElement, self).__init__() self._download_status = download_status + # XXX: fun fact: the `get_results()` method which we wind up + # invoking here (see immutable.downloader.status.DownloadStatus) + # is unimplemented, and simply returns a `None`. As a result, + # `results()` renderer returns an empty tag, and does not invoke + # any of the subsequent renderers. Thus we end up not displaying + # download results on the download status page. def download_results(self): return defer.maybeDeferred(self._download_status.get_results) From 88e8854ad47c13249c346e4c5abfc6b7022a62ca Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 15:28:00 -0400 Subject: [PATCH 0117/1054] Wrap renderer results in tags --- src/allmydata/web/status.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 83e588bd9..3d8ecb975 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -683,39 +683,39 @@ class DownloadStatusElement(Element): @renderer def time_total(self, req, tag): - return self._get_time("total") + return tag(str(self._get_time("total"))) @renderer def time_peer_selection(self, req, tag): - return self._get_time("peer_selection") + return tag(str(self._get_time("peer_selection"))) @renderer def time_uri_extension(self, req, tag): - return self._get_time("uri_extension") + return tag(str(self._get_time("uri_extension"))) @renderer def time_hashtrees(self, req, tag): - return self._get_time("hashtrees") + return tag(str(self._get_time("hashtrees"))) @renderer def time_segments(self, req, tag): - return self._get_time("segments") + return tag(str(self._get_time("segments"))) @renderer def time_cumulative_fetch(self, req, tag): - return self._get_time("cumulative_fetch") + return tag(str(self._get_time("cumulative_fetch"))) @renderer def time_cumulative_decode(self, req, tag): - return self._get_time("cumulative_decode") + return tag(str(self._get_time("cumulative_decode"))) @renderer def time_cumulative_decrypt(self, req, tag): - return self._get_time("cumulative_decrypt") + return tag(str(self._get_time("cumulative_decrypt"))) @renderer def time_paused(self, req, tag): - return self._get_time("paused") + return tag(str(self._get_time("paused"))) def _get_rate(self, name): d = self.download_results() @@ -728,23 +728,23 @@ class DownloadStatusElement(Element): @renderer def rate_total(self, req, tag): - return self._get_rate("total") + return tag(str(self._get_rate("total"))) @renderer def rate_segments(self, req, tag): - return self._get_rate("segments") + return tag(str(self._get_rate("segments"))) @renderer def rate_fetch(self, req, tag): - return self._get_rate("cumulative_fetch") + return tag(str(self._get_rate("cumulative_fetch"))) @renderer def rate_decode(self, req, tag): - return self._get_rate("cumulative_decode") + return tag(str(self._get_rate("cumulative_decode"))) @renderer def rate_decrypt(self, req, tag): - return self._get_rate("cumulative_decrypt") + return tag(str(self._get_rate("cumulative_decrypt"))) @renderer def server_timings(self, req, tag): @@ -756,7 +756,7 @@ class DownloadStatusElement(Element): ul = tags.ul() for peerid in sorted(per_server.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) - times_s = ", ".join([self.render_time(None, t) + times_s = ", ".join([abbreviate_time(t) for t in per_server[peerid]]) ul(tags.li("[%s]: %s" % (peerid_s, times_s))) return tags.li("Per-Server Segment Fetch Response Times: ", ul) From ab4811eaa0c5ad48d1920bac90fceeb973d29407 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 15:57:59 -0400 Subject: [PATCH 0118/1054] Move tests for abbreviate time/rate methods to utils tests RateAndTimeMixin.render_time() is really abbreviate_time(), and RateAndTimeMixin.render_rate() is really abbreviate_rate(). This change moves the tests to their rightful place, and exercises them using the right names. We're also trying to avoid use of mixins. RateAndTimeMixin will eventually go. --- src/allmydata/test/web/test_util.py | 6 ++++++ src/allmydata/test/web/test_web.py | 11 ----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/allmydata/test/web/test_util.py b/src/allmydata/test/web/test_util.py index 2a6f67abc..0e75b0d70 100644 --- a/src/allmydata/test/web/test_util.py +++ b/src/allmydata/test/web/test_util.py @@ -22,6 +22,9 @@ class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase): self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms") self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us") self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us") + self.failUnlessReallyEqual(common.abbreviate_time(2.5), "2.50s") + self.failUnlessReallyEqual(common.abbreviate_time(0.25), "250ms") + self.failUnlessReallyEqual(common.abbreviate_time(0.0021), "2.1ms") def test_compute_rate(self): self.failUnlessReallyEqual(common.compute_rate(None, None), None) @@ -44,6 +47,9 @@ class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase): self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps") self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps") self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps") + self.failUnlessReallyEqual(common.abbreviate_rate(2500000), "2.50MBps") + self.failUnlessReallyEqual(common.abbreviate_rate(30100), "30.1kBps") + self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps") def test_abbreviate_size(self): self.failUnlessReallyEqual(common.abbreviate_size(None), "") diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py index 5be0b2f7b..d7b606263 100644 --- a/src/allmydata/test/web/test_web.py +++ b/src/allmydata/test/web/test_web.py @@ -1035,17 +1035,6 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi return d def test_status_numbers(self): - drrm = status.DownloadResultsRendererMixin() - self.failUnlessReallyEqual(drrm.render_time(None, None), "") - self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s") - self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms") - self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms") - self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us") - self.failUnlessReallyEqual(drrm.render_rate(None, None), "") - self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps") - self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps") - self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps") - urrm = status.UploadResultsRendererMixin() self.failUnlessReallyEqual(urrm.render_time(None, None), "") self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s") From 19e40e2193dd0f0268899a068e12774de7fc41f1 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 16:04:26 -0400 Subject: [PATCH 0119/1054] Add newsfragment --- newsfragments/3288.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3288.minor diff --git a/newsfragments/3288.minor b/newsfragments/3288.minor new file mode 100644 index 000000000..e69de29bb From c5342d8ae68acfcdf319ee079f7061ed5528bc20 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 14 May 2020 16:48:43 -0400 Subject: [PATCH 0120/1054] Add comments about events tables --- src/allmydata/web/status.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 3d8ecb975..4916030d2 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -431,6 +431,7 @@ class DownloadStatusElement(Element): evtag = tags.div() + # "DYHB Requests" table. dyhbtag = tags.table(align="left", class_="status-download-events") dyhbtag(tags.tr(tags.th("serverid"), @@ -461,6 +462,7 @@ class DownloadStatusElement(Element): evtag(tags.h2("DYHB Requests:"), dyhbtag) evtag(tags.br(clear="all")) + # "Read Events" table. readtag = tags.table(align="left",class_="status-download-events") readtag(tags.tr(( @@ -501,6 +503,7 @@ class DownloadStatusElement(Element): evtag(tags.h2("Read Events:"), readtag) evtag(tags.br(clear="all")) + # "Segment Events" table. segtag = tags.table(align="left",class_="status-download-events") segtag(tags.tr( @@ -546,6 +549,7 @@ class DownloadStatusElement(Element): evtag(tags.h2("Segment Events:"), segtag) evtag(tags.br(clear="all")) + # "Requests" table. reqtab = tags.table(align="left",class_="status-download-events") reqtab(tags.tr( From 993b1e225f6a9038d28d98b601a5ebbb692b2205 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 11:01:14 -0400 Subject: [PATCH 0121/1054] Use transparent tags to render values --- src/allmydata/web/download-status.xhtml | 44 ++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/allmydata/web/download-status.xhtml b/src/allmydata/web/download-status.xhtml index 93bd08408..14fbb4b9e 100644 --- a/src/allmydata/web/download-status.xhtml +++ b/src/allmydata/web/download-status.xhtml @@ -12,12 +12,12 @@

              File Download Status

                -
              • Started:
              • -
              • Storage Index:
              • -
              • Helper?:
              • -
              • Total Size:
              • -
              • Progress:
              • -
              • Status:
              • +
              • Started:
              • +
              • Storage Index:
              • +
              • Helper?:
              • +
              • Total Size:
              • +
              • Progress:
              • +
              • Status:
              @@ -28,28 +28,28 @@
              • -
              • Servermap:
              • +
              • Servermap:
              • Timings:
                • -
                • File Size: bytes
                • -
                • Total: - ()
                • +
                • File Size: bytes
                • +
                • Total: + ()
                  • -
                  • Peer Selection:
                  • -
                  • UEB Fetch:
                  • -
                  • Hashtree Fetch:
                  • -
                  • Segment Fetch: - ()
                  • +
                  • Peer Selection:
                  • +
                  • UEB Fetch:
                  • +
                  • Hashtree Fetch:
                  • +
                  • Segment Fetch: + ()
                    • -
                    • Cumulative Fetching: - ()
                    • -
                    • Cumulative Decoding: - ()
                    • -
                    • Cumulative Decrypting: - ()
                    • +
                    • Cumulative Fetching: + ()
                    • +
                    • Cumulative Decoding: + ()
                    • +
                    • Cumulative Decrypting: + ()
                    -
                  • Paused by client:
                  • +
                  • Paused by client:
                From 0b3db59742ae727c682bb8a4f42022ef9fc4d6f5 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 13:30:39 -0400 Subject: [PATCH 0122/1054] Add related ticket to comment about unimplemented method --- src/allmydata/web/status.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 4916030d2..c9fa4f1f0 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -396,6 +396,8 @@ class DownloadStatusElement(Element): # `results()` renderer returns an empty tag, and does not invoke # any of the subsequent renderers. Thus we end up not displaying # download results on the download status page. + # + # See #3310: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3310 def download_results(self): return defer.maybeDeferred(self._download_status.get_results) From c23fedbcfeec5d465e2c9719f6b42a6ade4e1fa1 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 13:36:30 -0400 Subject: [PATCH 0123/1054] Add related ticket to comment about refactoring --- src/allmydata/web/status.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index c9fa4f1f0..e6e422f6f 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -424,6 +424,8 @@ class DownloadStatusElement(Element): # XXX: This method is a candidate for refactoring. It renders # four tables from this function. Layout part of those tables # could be moved to download-status.xhtml. + # + # See #3311: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3311 @renderer def events(self, req, tag): if not self._download_status.storage_index: From aec6e5a9d4ca9f1a8bb8416e43642119b2160e45 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 20:03:58 -0400 Subject: [PATCH 0124/1054] Force a CircleCI build --- src/allmydata/web/download-status.xhtml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/allmydata/web/download-status.xhtml b/src/allmydata/web/download-status.xhtml index 14fbb4b9e..0c2da523c 100644 --- a/src/allmydata/web/download-status.xhtml +++ b/src/allmydata/web/download-status.xhtml @@ -59,5 +59,4 @@
                Return to the Welcome Page
                - From aac8dee8a0dd0aeab9724800c306b2538bbcae1f Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 17:07:47 -0400 Subject: [PATCH 0125/1054] Use twisted template tags in upload status page template --- src/allmydata/web/upload-status.xhtml | 62 +++++++++++++-------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/allmydata/web/upload-status.xhtml b/src/allmydata/web/upload-status.xhtml index 8aaecd47a..95a5f7692 100644 --- a/src/allmydata/web/upload-status.xhtml +++ b/src/allmydata/web/upload-status.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - File Upload Status @@ -10,46 +10,46 @@

                File Upload Status

                  -
                • Started:
                • -
                • Storage Index:
                • -
                • Helper?:
                • -
                • Total Size:
                • -
                • Progress (Hash):
                • -
                • Progress (Ciphertext):
                • -
                • Progress (Encode+Push):
                • -
                • Status:
                • +
                • Started:
                • +
                • Storage Index:
                • +
                • Helper?:
                • +
                • Total Size:
                • +
                • Progress (Hash):
                • +
                • Progress (Ciphertext):
                • +
                • Progress (Encode+Push):
                • +
                • Status:
                -
                +

                Upload Results

                  -
                • Shares Pushed:
                • -
                • Shares Already Present:
                • -
                • Sharemap:
                • -
                • Servermap:
                • +
                • Shares Pushed:
                • +
                • Shares Already Present:
                • +
                • Sharemap:
                • +
                • Servermap:
                • Timings:
                  • -
                  • File Size: bytes
                  • -
                  • Total: - ()
                  • +
                  • File Size: bytes
                  • +
                  • Total: + ()
                    • -
                    • Storage Index: - ()
                    • -
                    • [Contacting Helper]:
                    • -
                    • [Upload Ciphertext To Helper]: - ()
                    • +
                    • Storage Index: + ()
                    • +
                    • [Contacting Helper]:
                    • +
                    • [Upload Ciphertext To Helper]: + ()
                    • -
                    • Peer Selection:
                    • -
                    • Encode And Push: - ()
                    • +
                    • Peer Selection:
                    • +
                    • Encode And Push: + ()
                      • -
                      • Cumulative Encoding: - ()
                      • -
                      • Cumulative Pushing: - ()
                      • -
                      • Send Hashes And Close:
                      • +
                      • Cumulative Encoding: + ()
                      • +
                      • Cumulative Pushing: + ()
                      • +
                      • Send Hashes And Close:
                      -
                    • [Helper Total]:
                    • +
                    • [Helper Total]:
                From 41300097c8db4b8ad6c3ed709c1fd78b0eb3d2ec Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 17:08:31 -0400 Subject: [PATCH 0126/1054] Make UploadStatusPage a MultiFormatResource --- src/allmydata/web/status.py | 85 +++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index b20aa679e..2c284caf6 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -160,62 +160,85 @@ class UploadResultsRendererMixin(RateAndTimeMixin): d.addCallback(_convert) return d -class UploadStatusPage(UploadResultsRendererMixin, rend.Page): - docFactory = getxmlfile("upload-status.xhtml") - def __init__(self, data): - rend.Page.__init__(self, data) - self.upload_status = data +class UploadStatusPage(MultiFormatResource): + + def __init__(self, upload_status): + super(UploadStatusPage, self).__init__() + self._upload_status = upload_status + + def render_HTML(self, req): + elem = UploadStatusElement(self._upload_status) + return renderElement(req, elem) + + +class UploadStatusElement(Element, UploadResultsRendererMixin): + + loader = XMLFile(FilePath(__file__).sibling("upload-status.xhtml")) + + def __init__(self, upload_status): + super(UploadStatusElement, self).__init__() + self._upload_status = upload_status def upload_results(self): - return defer.maybeDeferred(self.upload_status.get_results) + return defer.maybeDeferred(self._upload_status.get_results) - def render_results(self, ctx, data): + @renderer + def results(self, req, tag): d = self.upload_results() def _got_results(results): if results: - return ctx.tag + return tag return "" d.addCallback(_got_results) return d - def render_started(self, ctx, data): - started_s = render_time(data.get_started()) - return started_s + @renderer + def started(self, req, tag): + started_s = render_time(self._upload_status.get_started()) + return tag(started_s) - def render_si(self, ctx, data): - si_s = base32.b2a_or_none(data.get_storage_index()) + @renderer + def si(self, req, tag): + si_s = base32.b2a_or_none(self._upload_status.get_storage_index()) if si_s is None: si_s = "(None)" - return si_s + return tag(str(si_s)) - def render_helper(self, ctx, data): - return {True: "Yes", - False: "No"}[data.using_helper()] + @renderer + def helper(self, req, tag): + return tag({True: "Yes", + False: "No"}[self._upload_status.using_helper()]) - def render_total_size(self, ctx, data): - size = data.get_size() + @renderer + def total_size(self, req, tag): + size = self._upload_status.get_size() if size is None: return "(unknown)" - return size + return tag(str(size)) - def render_progress_hash(self, ctx, data): - progress = data.get_progress()[0] + @renderer + def progress_hash(self, req, tag): + progress = self._upload_status.get_progress()[0] + # TODO: make an ascii-art bar + return tag("%.1f%%" % (100.0 * progress)) + + @renderer + def progress_ciphertext(self, req, tag): + progress = self._upload_status.get_progress()[1] # TODO: make an ascii-art bar return "%.1f%%" % (100.0 * progress) - def render_progress_ciphertext(self, ctx, data): - progress = data.get_progress()[1] + @renderer + def progress_encode_push(self, req, tag): + progress = self._upload_status.get_progress()[2] # TODO: make an ascii-art bar - return "%.1f%%" % (100.0 * progress) + return tag("%.1f%%" % (100.0 * progress)) - def render_progress_encode_push(self, ctx, data): - progress = data.get_progress()[2] - # TODO: make an ascii-art bar - return "%.1f%%" % (100.0 * progress) + @renderer + def status(self, req, tag): + return tag(self._upload_status.get_status()) - def render_status(self, ctx, data): - return data.get_status() class DownloadResultsRendererMixin(RateAndTimeMixin): # this requires a method named 'download_results' From 647071869173646a2567a9ba844bd8bbdd5884bb Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 17:50:32 -0400 Subject: [PATCH 0127/1054] Use twisted template tags in upload results page template --- src/allmydata/web/upload-results.xhtml | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/allmydata/web/upload-results.xhtml b/src/allmydata/web/upload-results.xhtml index fa8894dcd..290c9bd42 100644 --- a/src/allmydata/web/upload-results.xhtml +++ b/src/allmydata/web/upload-results.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - File Uploaded @@ -7,37 +7,37 @@ -

                Uploading File...

                +

                Uploading File...

                Upload Results:

                  -
                • URI:
                • -
                • Download link:
                • -
                • Sharemap:
                • -
                • Servermap:
                • +
                • URI:
                • +
                • Download link:
                • +
                • Sharemap:
                • +
                • Servermap:
                • Timings:
                  • -
                  • File Size: bytes
                  • -
                  • Total: - ()
                  • +
                  • File Size: bytes
                  • +
                  • Total: + ()
                    • -
                    • Storage Index: - ()
                    • -
                    • [Contacting Helper]:
                    • -
                    • [Upload Ciphertext To Helper]: - ()
                    • +
                    • Storage Index: + ()
                    • +
                    • [Contacting Helper]:
                    • +
                    • [Upload Ciphertext To Helper]: + ()
                    • -
                    • Peer Selection:
                    • -
                    • Encode And Push: - ()
                    • +
                    • Peer Selection:
                    • +
                    • Encode And Push: + ()
                      • -
                      • Cumulative Encoding: - ()
                      • -
                      • Cumulative Pushing: - ()
                      • -
                      • Send Hashes And Close:
                      • +
                      • Cumulative Encoding: + ()
                      • +
                      • Cumulative Pushing: + ()
                      • +
                      • Send Hashes And Close:
                      -
                    • [Helper Total]:
                    • +
                    • [Helper Total]:
                From 57b1203d17a713ad58fc5b2e0698ba073f0ce349 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 17:50:54 -0400 Subject: [PATCH 0128/1054] Make UploadResultsPage a MultiFormatResource --- src/allmydata/web/unlinked.py | 58 +++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index 8a84a4021..0abf6cc6d 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -2,11 +2,27 @@ import urllib from twisted.web import http from twisted.internet import defer -from nevow import rend, url, tags as T +from twisted.python.filepath import FilePath +from twisted.web.template import ( + Element, + XMLFile, + renderer, + renderElement, + tags, +) +from nevow import url, tags as T from allmydata.immutable.upload import FileHandle from allmydata.mutable.publish import MutableFileHandle -from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \ - convert_children_json, WebError, get_format, get_mutable_type +from allmydata.web.common import ( + getxmlfile, + get_arg, + boolean_of_arg, + convert_children_json, + WebError, + get_format, + get_mutable_type, + MultiFormatResource, +) from allmydata.web import status def PUTUnlinkedCHK(req, client): @@ -59,34 +75,50 @@ def POSTUnlinkedCHK(req, client): return d -class UploadResultsPage(status.UploadResultsRendererMixin, rend.Page): +class UploadResultsPage(MultiFormatResource): """'POST /uri', to create an unlinked file.""" - docFactory = getxmlfile("upload-results.xhtml") def __init__(self, upload_results): - rend.Page.__init__(self) - self.results = upload_results + super(UploadResultsPage, self).__init__() + self._upload_results = upload_results + + def render_HTML(self, req): + elem = UploadResultsElement(self._upload_results) + return renderElement(req, elem) + + +class UploadResultsElement(Element, status.UploadResultsRendererMixin): + + loader = XMLFile(FilePath(__file__).sibling("upload-results.xhtml")) + + def __init__(self, upload_results): + super(UploadResultsElement, self).__init__() + self._upload_results = upload_results def upload_results(self): - return defer.succeed(self.results) + return defer.succeed(self._upload_results) - def data_done(self, ctx, data): + @renderer + def done(self, req, tag): d = self.upload_results() d.addCallback(lambda res: "done!") return d - def data_uri(self, ctx, data): + @renderer + def uri(self, req, tag): d = self.upload_results() d.addCallback(lambda res: res.get_uri()) return d - def render_download_link(self, ctx, data): + @renderer + def download_link(self, req, tag): d = self.upload_results() d.addCallback(lambda res: - T.a(href="/uri/" + urllib.quote(res.get_uri())) - ["/uri/" + res.get_uri()]) + tags.a("/uri/" + res.get_uri(), + href="/uri/" + urllib.quote(res.get_uri()))) return d + def POSTUnlinkedSSK(req, client, version): # "POST /uri", to create an unlinked file. # SDMF: files are small, and we can only upload data From 3bfa5a5c292c33ff7eb4679938863aa039257d19 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 17:53:38 -0400 Subject: [PATCH 0129/1054] Use twisted web template in UploadResultsRendererMixin --- src/allmydata/web/status.py | 120 +++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 2c284caf6..4edc63234 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -35,128 +35,150 @@ class RateAndTimeMixin(object): def render_rate(self, ctx, data): return abbreviate_rate(data) + class UploadResultsRendererMixin(RateAndTimeMixin): # this requires a method named 'upload_results' - def render_pushed_shares(self, ctx, data): + @renderer + def pushed_shares(self, req, tag): d = self.upload_results() - d.addCallback(lambda res: res.get_pushed_shares()) + d.addCallback(lambda res: str(res.get_pushed_shares())) return d - def render_preexisting_shares(self, ctx, data): + @renderer + def preexisting_shares(self, req, tag): d = self.upload_results() - d.addCallback(lambda res: res.get_preexisting_shares()) + d.addCallback(lambda res: str(res.get_preexisting_shares())) return d - def render_sharemap(self, ctx, data): + @renderer + def sharemap(self, req, tag): d = self.upload_results() d.addCallback(lambda res: res.get_sharemap()) def _render(sharemap): if sharemap is None: return "None" - l = T.ul() + ul = tags.ul() for shnum, servers in sorted(sharemap.items()): server_names = ', '.join([s.get_name() for s in servers]) - l[T.li["%d -> placed on [%s]" % (shnum, server_names)]] - return l + ul(tags.li("%d -> placed on [%s]" % (shnum, server_names))) + return ul d.addCallback(_render) return d - def render_servermap(self, ctx, data): + @renderer + def servermap(self, req, tag): d = self.upload_results() d.addCallback(lambda res: res.get_servermap()) def _render(servermap): if servermap is None: return "None" - l = T.ul() + ul = tags.ul() for server, shnums in sorted(servermap.items()): shares_s = ",".join(["#%d" % shnum for shnum in shnums]) - l[T.li["[%s] got share%s: %s" % (server.get_name(), - plural(shnums), shares_s)]] - return l + ul(tags.li("[%s] got share%s: %s" % (server.get_name(), + plural(shnums), shares_s))) + return ul d.addCallback(_render) return d - def data_file_size(self, ctx, data): + @renderer + def file_size(self, req, tag): d = self.upload_results() - d.addCallback(lambda res: res.get_file_size()) + d.addCallback(lambda res: str(res.get_file_size())) return d def _get_time(self, name): d = self.upload_results() - d.addCallback(lambda res: res.get_timings().get(name)) + d.addCallback(lambda res: str(res.get_timings().get(name))) return d - def data_time_total(self, ctx, data): - return self._get_time("total") + @renderer + def time_total(self, req, tag): + return tag(self._get_time("total")) - def data_time_storage_index(self, ctx, data): - return self._get_time("storage_index") + @renderer + def time_storage_index(self, req, tag): + return tag(self._get_time("storage_index")) - def data_time_contacting_helper(self, ctx, data): - return self._get_time("contacting_helper") + @renderer + def time_contacting_helper(self, req, tag): + return tag(self._get_time("contacting_helper")) - def data_time_cumulative_fetch(self, ctx, data): - return self._get_time("cumulative_fetch") + @renderer + def time_cumulative_fetch(self, req, tag): + return tag(self._get_time("cumulative_fetch")) - def data_time_helper_total(self, ctx, data): - return self._get_time("helper_total") + @renderer + def time_helper_total(self, req, tag): + return tag(self._get_time("helper_total")) - def data_time_peer_selection(self, ctx, data): - return self._get_time("peer_selection") + @renderer + def time_peer_selection(self, req, tag): + return tag(self._get_time("peer_selection")) - def data_time_total_encode_and_push(self, ctx, data): - return self._get_time("total_encode_and_push") + @renderer + def time_total_encode_and_push(self, req, tag): + return tag(self._get_time("total_encode_and_push")) - def data_time_cumulative_encoding(self, ctx, data): - return self._get_time("cumulative_encoding") + @renderer + def time_cumulative_encoding(self, req, tag): + return tag(self._get_time("cumulative_encoding")) - def data_time_cumulative_sending(self, ctx, data): - return self._get_time("cumulative_sending") + @renderer + def time_cumulative_sending(self, req, tag): + return tag(self._get_time("cumulative_sending")) - def data_time_hashes_and_close(self, ctx, data): - return self._get_time("hashes_and_close") + @renderer + def time_hashes_and_close(self, req, tag): + return tag(self._get_time("hashes_and_close")) def _get_rate(self, name): d = self.upload_results() def _convert(r): file_size = r.get_file_size() duration = r.get_timings().get(name) - return compute_rate(file_size, duration) + return str(compute_rate(file_size, duration)) d.addCallback(_convert) return d - def data_rate_total(self, ctx, data): - return self._get_rate("total") + @renderer + def rate_total(self, req, tag): + return tag(self._get_rate("total")) - def data_rate_storage_index(self, ctx, data): - return self._get_rate("storage_index") + @renderer + def rate_storage_index(self, req, tag): + return tag(self._get_rate("storage_index")) - def data_rate_encode(self, ctx, data): - return self._get_rate("cumulative_encoding") + @renderer + def rate_encode(self, req, tag): + return tag(self._get_rate("cumulative_encoding")) - def data_rate_push(self, ctx, data): + @renderer + def rate_push(self, req, tag): return self._get_rate("cumulative_sending") - def data_rate_encode_and_push(self, ctx, data): + @renderer + def rate_encode_and_push(self, req, tag): d = self.upload_results() def _convert(r): file_size = r.get_file_size() time1 = r.get_timings().get("cumulative_encoding") time2 = r.get_timings().get("cumulative_sending") if (time1 is None or time2 is None): - return None + return str(None) else: - return compute_rate(file_size, time1+time2) + return str(compute_rate(file_size, time1+time2)) d.addCallback(_convert) return d - def data_rate_ciphertext_fetch(self, ctx, data): + @renderer + def rate_ciphertext_fetch(self, req, tag): d = self.upload_results() def _convert(r): fetch_size = r.get_ciphertext_fetched() duration = r.get_timings().get("cumulative_fetch") - return compute_rate(fetch_size, duration) + return str(compute_rate(fetch_size, duration)) d.addCallback(_convert) return d From 7a82fd671f4377c569957beb234a8c5cc55254b9 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Mon, 18 May 2020 18:00:20 -0400 Subject: [PATCH 0130/1054] Handle "t=upload" in UploadResultsPage POST handling --- src/allmydata/web/unlinked.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index 0abf6cc6d..283d8d586 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -86,6 +86,23 @@ class UploadResultsPage(MultiFormatResource): elem = UploadResultsElement(self._upload_results) return renderElement(req, elem) + # This is weird but necessary because: + # + # 1. MultiFormatResource.render() uses argument "t" to figure out + # its output format. + # + # 2. Upload request is of the form "POST /uri?t=upload&file=newfile". + # See URIHandler.render_POST(). + # + # MultiFormatResource.render() looks up "t" argument, which in + # this case has the value "upload", and then it would look for a + # render_UPLOAD() method. + # + # We could change upload request to use more descriptive names + # that do not cause name collisions like this. That should be a + # separate change though. + render_UPLOAD = render_HTML + class UploadResultsElement(Element, status.UploadResultsRendererMixin): From 54ac2d224ae449a8da651139a3f60a80de0ed664 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 13:23:23 -0400 Subject: [PATCH 0131/1054] Remove unused imports --- src/allmydata/web/unlinked.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index 283d8d586..33fb7bafa 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -10,11 +10,10 @@ from twisted.web.template import ( renderElement, tags, ) -from nevow import url, tags as T +from nevow import url from allmydata.immutable.upload import FileHandle from allmydata.mutable.publish import MutableFileHandle from allmydata.web.common import ( - getxmlfile, get_arg, boolean_of_arg, convert_children_json, From 5fdd61b1464f35b2a5df056d04540eab599917d4 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 13:25:09 -0400 Subject: [PATCH 0132/1054] Document UploadStatusPage --- src/allmydata/web/status.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 4edc63234..be87cff65 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -184,8 +184,12 @@ class UploadResultsRendererMixin(RateAndTimeMixin): class UploadStatusPage(MultiFormatResource): + """Renders /status/up-%d.""" def __init__(self, upload_status): + """ + :param IUploadStatus upload_status: stats provider. + """ super(UploadStatusPage, self).__init__() self._upload_status = upload_status From 7be18839104b025d78e00f9105714936455a7df4 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 13:28:16 -0400 Subject: [PATCH 0133/1054] Document UploadResultsPage parameter --- src/allmydata/web/unlinked.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index 33fb7bafa..6ca219c8b 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -78,6 +78,9 @@ class UploadResultsPage(MultiFormatResource): """'POST /uri', to create an unlinked file.""" def __init__(self, upload_results): + """ + :param IUploadResults upload_results: stats provider. + """ super(UploadResultsPage, self).__init__() self._upload_results = upload_results From aecd90858a852e242e990729e97f9d9a5af22c51 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 14:53:33 -0400 Subject: [PATCH 0134/1054] Render abbreviated rate and time values --- src/allmydata/web/status.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index be87cff65..61ab0ae26 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -90,7 +90,7 @@ class UploadResultsRendererMixin(RateAndTimeMixin): def _get_time(self, name): d = self.upload_results() - d.addCallback(lambda res: str(res.get_timings().get(name))) + d.addCallback(lambda res: abbreviate_time(res.get_timings().get(name))) return d @renderer @@ -138,7 +138,7 @@ class UploadResultsRendererMixin(RateAndTimeMixin): def _convert(r): file_size = r.get_file_size() duration = r.get_timings().get(name) - return str(compute_rate(file_size, duration)) + return abbreviate_rate(compute_rate(file_size, duration)) d.addCallback(_convert) return d @@ -166,9 +166,9 @@ class UploadResultsRendererMixin(RateAndTimeMixin): time1 = r.get_timings().get("cumulative_encoding") time2 = r.get_timings().get("cumulative_sending") if (time1 is None or time2 is None): - return str(None) + return abbreviate_rate(None) else: - return str(compute_rate(file_size, time1+time2)) + return abbreviate_rate(compute_rate(file_size, time1+time2)) d.addCallback(_convert) return d @@ -178,7 +178,7 @@ class UploadResultsRendererMixin(RateAndTimeMixin): def _convert(r): fetch_size = r.get_ciphertext_fetched() duration = r.get_timings().get("cumulative_fetch") - return str(compute_rate(fetch_size, duration)) + return abbreviate_rate(compute_rate(fetch_size, duration)) d.addCallback(_convert) return d From 00f70f6539f3360806feca100802fb8deabc16f3 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 20:08:34 -0400 Subject: [PATCH 0135/1054] Add newsfragment --- newsfragments/3287.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3287.minor diff --git a/newsfragments/3287.minor b/newsfragments/3287.minor new file mode 100644 index 000000000..e69de29bb From 91f8d939f8dd5510c2542178f0326800d66a51f0 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 20:22:59 -0400 Subject: [PATCH 0136/1054] Avoid use of RateAndTimeMixin in UploadResultsRendererMixin --- src/allmydata/web/status.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 61ab0ae26..eb7fedc6f 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -36,7 +36,7 @@ class RateAndTimeMixin(object): return abbreviate_rate(data) -class UploadResultsRendererMixin(RateAndTimeMixin): +class UploadResultsRendererMixin(object): # this requires a method named 'upload_results' @renderer @@ -383,7 +383,7 @@ class DownloadResultsRendererMixin(RateAndTimeMixin): l = T.ul() for peerid in sorted(per_server.keys()): peerid_s = idlib.shortnodeid_b2a(peerid) - times_s = ", ".join([self.render_time(None, t) + times_s = ", ".join([abbreviate_time(t) for t in per_server[peerid]]) l[T.li["[%s]: %s" % (peerid_s, times_s)]] return T.li["Per-Server Segment Fetch Response Times: ", l] From b0e3325d03e6af3d2bd1556a91335ef8767b895a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 20:49:34 -0400 Subject: [PATCH 0137/1054] Move time formatter tests UploadResultsRendererMixin no longer has render_time(); moving tests for render_time() as tests for abbreviate_time() to a more appropriate place. --- src/allmydata/test/web/test_util.py | 10 ++++++++++ src/allmydata/test/web/test_web.py | 11 ----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/allmydata/test/web/test_util.py b/src/allmydata/test/web/test_util.py index 2a6f67abc..a378c1447 100644 --- a/src/allmydata/test/web/test_util.py +++ b/src/allmydata/test/web/test_util.py @@ -23,6 +23,16 @@ class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase): self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us") self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us") + self.failUnlessReallyEqual(common.abbreviate_time(None), "") + self.failUnlessReallyEqual(common.abbreviate_time(2.5), "2.50s") + self.failUnlessReallyEqual(common.abbreviate_time(0.25), "250ms") + self.failUnlessReallyEqual(common.abbreviate_time(0.0021), "2.1ms") + self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us") + self.failUnlessReallyEqual(common.abbreviate_rate(None), "") + self.failUnlessReallyEqual(common.abbreviate_rate(2500000), "2.50MBps") + self.failUnlessReallyEqual(common.abbreviate_rate(30100), "30.1kBps") + self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps") + def test_compute_rate(self): self.failUnlessReallyEqual(common.compute_rate(None, None), None) self.failUnlessReallyEqual(common.compute_rate(None, 1), None) diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py index 5be0b2f7b..d7137ad11 100644 --- a/src/allmydata/test/web/test_web.py +++ b/src/allmydata/test/web/test_web.py @@ -1046,17 +1046,6 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixi self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps") self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps") - urrm = status.UploadResultsRendererMixin() - self.failUnlessReallyEqual(urrm.render_time(None, None), "") - self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s") - self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms") - self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms") - self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us") - self.failUnlessReallyEqual(urrm.render_rate(None, None), "") - self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps") - self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps") - self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps") - def test_GET_FILEURL(self): d = self.GET(self.public_url + "/foo/bar.txt") d.addCallback(self.failUnlessIsBarDotTxt) From f8562530be3d6c0a07b180a74558b818fadd8ffb Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 21:11:42 -0400 Subject: [PATCH 0138/1054] Make Element the superclass of UploadResultsRendererMixin --- src/allmydata/web/status.py | 4 ++-- src/allmydata/web/unlinked.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index eb7fedc6f..8147437fa 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -36,7 +36,7 @@ class RateAndTimeMixin(object): return abbreviate_rate(data) -class UploadResultsRendererMixin(object): +class UploadResultsRendererMixin(Element): # this requires a method named 'upload_results' @renderer @@ -198,7 +198,7 @@ class UploadStatusPage(MultiFormatResource): return renderElement(req, elem) -class UploadStatusElement(Element, UploadResultsRendererMixin): +class UploadStatusElement(UploadResultsRendererMixin): loader = XMLFile(FilePath(__file__).sibling("upload-status.xhtml")) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index 6ca219c8b..d938691a4 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -106,7 +106,7 @@ class UploadResultsPage(MultiFormatResource): render_UPLOAD = render_HTML -class UploadResultsElement(Element, status.UploadResultsRendererMixin): +class UploadResultsElement(status.UploadResultsRendererMixin): loader = XMLFile(FilePath(__file__).sibling("upload-results.xhtml")) From 9520ad71eb3c6ffa5d42b80d525f26936e79163b Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 21:24:21 -0400 Subject: [PATCH 0139/1054] Rearrange imports --- src/allmydata/web/status.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index b20aa679e..327d610a6 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -1,5 +1,7 @@ -import pprint, itertools, hashlib +import pprint +import itertools +import hashlib import json from twisted.internet import defer from twisted.python.filepath import FilePath @@ -24,8 +26,14 @@ from allmydata.web.common import ( MultiFormatPage, MultiFormatResource, ) -from allmydata.interfaces import IUploadStatus, IDownloadStatus, \ - IPublishStatus, IRetrieveStatus, IServermapUpdaterStatus + +from allmydata.interfaces import ( + IUploadStatus, + IDownloadStatus, + IPublishStatus, + IRetrieveStatus, + IServermapUpdaterStatus, +) class RateAndTimeMixin(object): From 03d529c22a91020b893fab8c28678918f3450b6d Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 21:33:30 -0400 Subject: [PATCH 0140/1054] Remove unused import --- src/allmydata/web/unlinked.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index d938691a4..91efff2c4 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -4,7 +4,6 @@ from twisted.web import http from twisted.internet import defer from twisted.python.filepath import FilePath from twisted.web.template import ( - Element, XMLFile, renderer, renderElement, From 81dc63b511704269b2038e7113b3ce753671512a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 21:32:44 -0400 Subject: [PATCH 0141/1054] Use twisted template tags in status page template --- src/allmydata/web/status.xhtml | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/allmydata/web/status.xhtml b/src/allmydata/web/status.xhtml index d827002f4..045d5aa85 100644 --- a/src/allmydata/web/status.xhtml +++ b/src/allmydata/web/status.xhtml @@ -1,4 +1,4 @@ - + Tahoe-LAFS - Recent and Active Operations @@ -11,8 +11,8 @@

                Active Operations:

                - - +
                + @@ -20,21 +20,21 @@ - - - - - - - + + + + + + + - +
                Type Storage Index Helper?Progress Status
                No active operations!
                No active operations!

                Recent Operations:

                - - +
                + @@ -43,16 +43,16 @@ - - - - - - - - + + + + + + + + - +
                Started Type Storage IndexProgress Status
                No recent operations!
                No recent operations!

                From 3357f4c5ceea5a0dcde94d0ff069914821dbde61 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 21:43:41 -0400 Subject: [PATCH 0142/1054] Make Status a MultiFormatResource --- src/allmydata/web/status.py | 89 ++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 327d610a6..e5165a570 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -1125,14 +1125,19 @@ def marshal_json(s): return item -class Status(MultiFormatPage): - docFactory = getxmlfile("status.xhtml") +class Status(MultiFormatResource): + addSlash = True def __init__(self, history): - rend.Page.__init__(self, history) + super(Status, self).__init__() self.history = history + def render_HTML(self, req): + elem = StatusElement(self._get_active_operations(), + self._get_recent_operations()) + return renderElement(req, elem) + def render_JSON(self, req): # modern browsers now render this instead of forcing downloads req.setHeader("content-type", "application/json") @@ -1180,7 +1185,51 @@ class Status(MultiFormatPage): recent.reverse() return recent - def render_row(self, ctx, data): + def childFactory(self, ctx, name): + h = self.history + try: + stype, count_s = name.split("-") + except ValueError: + raise RuntimeError( + "no - in '{}'".format(name) + ) + count = int(count_s) + if stype == "up": + for s in itertools.chain(h.list_all_upload_statuses(), + h.list_all_helper_statuses()): + # immutable-upload helpers use the same status object as a + # regular immutable-upload + if s.get_counter() == count: + return UploadStatusPage(s) + if stype == "down": + for s in h.list_all_download_statuses(): + if s.get_counter() == count: + return DownloadStatusPage(s) + if stype == "mapupdate": + for s in h.list_all_mapupdate_statuses(): + if s.get_counter() == count: + return MapupdateStatusPage(s) + if stype == "publish": + for s in h.list_all_publish_statuses(): + if s.get_counter() == count: + return PublishStatusPage(s) + if stype == "retrieve": + for s in h.list_all_retrieve_statuses(): + if s.get_counter() == count: + return RetrieveStatusPage(s) + + +class StatusElement(Element): + + loader = XMLFile(FilePath(__file__).sibling("status.xhtml")) + + def __init__(self, active, recent): + super(StatusElement, self).__init__() + self._active = active + self._recent = recent + + @renderer + def row(self, req, tag): s = data started_s = render_time(s.get_started()) @@ -1231,38 +1280,6 @@ class Status(MultiFormatPage): ctx.fillSlots("status", T.a(href=link)[s.get_status()]) return ctx.tag - def childFactory(self, ctx, name): - h = self.history - try: - stype, count_s = name.split("-") - except ValueError: - raise RuntimeError( - "no - in '{}'".format(name) - ) - count = int(count_s) - if stype == "up": - for s in itertools.chain(h.list_all_upload_statuses(), - h.list_all_helper_statuses()): - # immutable-upload helpers use the same status object as a - # regular immutable-upload - if s.get_counter() == count: - return UploadStatusPage(s) - if stype == "down": - for s in h.list_all_download_statuses(): - if s.get_counter() == count: - return DownloadStatusPage(s) - if stype == "mapupdate": - for s in h.list_all_mapupdate_statuses(): - if s.get_counter() == count: - return MapupdateStatusPage(s) - if stype == "publish": - for s in h.list_all_publish_statuses(): - if s.get_counter() == count: - return PublishStatusPage(s) - if stype == "retrieve": - for s in h.list_all_retrieve_statuses(): - if s.get_counter() == count: - return RetrieveStatusPage(s) # Render "/helper_status" page. class HelperStatus(MultiFormatResource): From a6e32135e866c8bb3621ae725137cb27953128df Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 22:10:40 -0400 Subject: [PATCH 0143/1054] Update status page child route handler and table renderers Drop nevow-isms and use twisted.web's way of doing things. --- src/allmydata/web/status.py | 162 ++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index e5165a570..1e0c81b98 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -25,6 +25,7 @@ from allmydata.web.common import ( render_time, MultiFormatPage, MultiFormatResource, + SlotsSequenceElement, ) from allmydata.interfaces import ( @@ -1127,8 +1128,6 @@ def marshal_json(s): class Status(MultiFormatResource): - addSlash = True - def __init__(self, history): super(Status, self).__init__() self.history = history @@ -1153,45 +1152,24 @@ class Status(MultiFormatResource): return json.dumps(data, indent=1) + "\n" - def _get_all_statuses(self): - h = self.history - return itertools.chain(h.list_all_upload_statuses(), - h.list_all_download_statuses(), - h.list_all_mapupdate_statuses(), - h.list_all_publish_statuses(), - h.list_all_retrieve_statuses(), - h.list_all_helper_statuses(), - ) + def getChild(self, path, request): + # The "if (path is empty) return self" line should handle + # trailing slash in request path. + # + # Twisted Web's documentation says this: "If the URL ends in a + # slash, for example ``http://example.com/foo/bar/`` , the + # final URL segment will be an empty string. Resources can + # thus know if they were requested with or without a final + # slash." + if not path: + return self - def data_active_operations(self, ctx, data): - return self._get_active_operations() - - def _get_active_operations(self): - active = [s - for s in self._get_all_statuses() - if s.get_active()] - active.sort(lambda a, b: cmp(a.get_started(), b.get_started())) - active.reverse() - return active - - def data_recent_operations(self, ctx, data): - return self._get_recent_operations() - - def _get_recent_operations(self): - recent = [s - for s in self._get_all_statuses() - if not s.get_active()] - recent.sort(lambda a, b: cmp(a.get_started(), b.get_started())) - recent.reverse() - return recent - - def childFactory(self, ctx, name): h = self.history try: - stype, count_s = name.split("-") + stype, count_s = path.split("-") except ValueError: raise RuntimeError( - "no - in '{}'".format(name) + "no - in '{}'".format(path) ) count = int(count_s) if stype == "up": @@ -1218,6 +1196,32 @@ class Status(MultiFormatResource): if s.get_counter() == count: return RetrieveStatusPage(s) + def _get_all_statuses(self): + h = self.history + return itertools.chain(h.list_all_upload_statuses(), + h.list_all_download_statuses(), + h.list_all_mapupdate_statuses(), + h.list_all_publish_statuses(), + h.list_all_retrieve_statuses(), + h.list_all_helper_statuses(), + ) + + def _get_active_operations(self): + active = [s + for s in self._get_all_statuses() + if s.get_active()] + active.sort(lambda a, b: cmp(a.get_started(), b.get_started())) + active.reverse() + return active + + def _get_recent_operations(self): + recent = [s + for s in self._get_all_statuses() + if not s.get_active()] + recent.sort(lambda a, b: cmp(a.get_started(), b.get_started())) + recent.reverse() + return recent + class StatusElement(Element): @@ -1229,56 +1233,70 @@ class StatusElement(Element): self._recent = recent @renderer - def row(self, req, tag): - s = data + def active_operations(self, req, tag): + active = [self.get_op_state(op) for op in self._active] + return SlotsSequenceElement(tag, active) - started_s = render_time(s.get_started()) - ctx.fillSlots("started", started_s) + @renderer + def recent_operations(self, req, tag): + active = [self.get_op_state(op) for op in self._recent] + return SlotsSequenceElement(tag, active) - si_s = base32.b2a_or_none(s.get_storage_index()) + @staticmethod + def get_op_state(op): + result = dict() + + started_s = render_time(op.get_started()) + result.update({"started": started_s}) + + si_s = base32.b2a_or_none(op.get_storage_index()) if si_s is None: si_s = "(None)" - ctx.fillSlots("si", si_s) - ctx.fillSlots("helper", {True: "Yes", - False: "No"}[s.using_helper()]) - size = s.get_size() + result.update({"si": si_s}) + result.update({"helper": + {True: "Yes", False: "No"}[op.using_helper()]}) + + size = op.get_size() if size is None: size = "(unknown)" elif isinstance(size, (int, long, float)): size = abbreviate_size(size) - ctx.fillSlots("total_size", size) - progress = data.get_progress() - if IUploadStatus.providedBy(data): - link = "up-%d" % data.get_counter() - ctx.fillSlots("type", "upload") + result.update({"total_size": size}) + + progress = op.get_progress() + if IUploadStatus.providedBy(op): + link = "up-%d" % op.get_counter() + result.update({"type": "upload"}) # TODO: make an ascii-art bar (chk, ciphertext, encandpush) = progress progress_s = ("hash: %.1f%%, ciphertext: %.1f%%, encode: %.1f%%" % - ( (100.0 * chk), - (100.0 * ciphertext), - (100.0 * encandpush) )) - ctx.fillSlots("progress", progress_s) - elif IDownloadStatus.providedBy(data): - link = "down-%d" % data.get_counter() - ctx.fillSlots("type", "download") - ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress)) - elif IPublishStatus.providedBy(data): - link = "publish-%d" % data.get_counter() - ctx.fillSlots("type", "publish") - ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress)) - elif IRetrieveStatus.providedBy(data): - ctx.fillSlots("type", "retrieve") - link = "retrieve-%d" % data.get_counter() - ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress)) + ((100.0 * chk), + (100.0 * ciphertext), + (100.0 * encandpush))) + result.update({"progress": progress_s}) + elif IDownloadStatus.providedBy(op): + link = "down-%d" % op.get_counter() + result.update({"type": "download"}) + result.update({"progress": "%.1f%%" % (100.0 * progress)}) + elif IPublishStatus.providedBy(op): + link = "publish-%d" % op.get_counter() + result.update({"type": "publish"}) + result.update({"progress": "%.1f%%" % (100.0 * progress)}) + elif IRetrieveStatus.providedBy(op): + result.update({"type": "retrieve"}) + link = "retrieve-%d" % op.get_counter() + result.update({"progress": "%.1f%%" % (100.0 * progress)}) else: - assert IServermapUpdaterStatus.providedBy(data) - ctx.fillSlots("type", "mapupdate %s" % data.get_mode()) - link = "mapupdate-%d" % data.get_counter() - ctx.fillSlots("progress", "%.1f%%" % (100.0 * progress)) - ctx.fillSlots("status", T.a(href=link)[s.get_status()]) - return ctx.tag + assert IServermapUpdaterStatus.providedBy(op) + result.update({"type": "mapupdate %s" % op.get_mode()}) + link = "mapupdate-%d" % op.get_counter() + result.update({"progress": "%.1f%%" % (100.0 * progress)}) + + result.update({"status": tags.a(op.get_status(), href=link)}) + + return result # Render "/helper_status" page. From bd799d9d726953c08b82ff011c5f84b5d98d987a Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 22:18:58 -0400 Subject: [PATCH 0144/1054] Add newsfragment --- newsfragments/3254.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3254.minor diff --git a/newsfragments/3254.minor b/newsfragments/3254.minor new file mode 100644 index 000000000..e69de29bb From 93d015e468dc427743de9dbf2cf35c67ca7ad189 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 22 May 2020 22:24:09 -0400 Subject: [PATCH 0145/1054] Document status.Status --- src/allmydata/web/status.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index 1e0c81b98..db127db59 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -1127,8 +1127,12 @@ def marshal_json(s): class Status(MultiFormatResource): + """Renders /status page.""" def __init__(self, history): + """ + :param allmydata.history.History history: provides operation statuses. + """ super(Status, self).__init__() self.history = history From d3f43d31d998fb97f8ad3e6fac007ed3fe57ab15 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Sat, 23 May 2020 06:17:06 -0400 Subject: [PATCH 0146/1054] Remove unused import --- src/allmydata/web/status.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py index db127db59..ec18426dc 100644 --- a/src/allmydata/web/status.py +++ b/src/allmydata/web/status.py @@ -23,7 +23,6 @@ from allmydata.web.common import ( plural, compute_rate, render_time, - MultiFormatPage, MultiFormatResource, SlotsSequenceElement, ) From d50ca9af55c2a19ccf35c4ff0b128ecafb75331f Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 20 May 2020 22:43:21 -0600 Subject: [PATCH 0147/1054] proof-of-concept verified-fake for testing --- integration/test_testing.py | 26 +++ src/allmydata/testing/__init__.py | 0 src/allmydata/testing/web.py | 263 ++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 integration/test_testing.py create mode 100644 src/allmydata/testing/__init__.py create mode 100644 src/allmydata/testing/web.py diff --git a/integration/test_testing.py b/integration/test_testing.py new file mode 100644 index 000000000..47498b1d7 --- /dev/null +++ b/integration/test_testing.py @@ -0,0 +1,26 @@ + + +from allmydata.testing.web import ( + create_fake_tahoe_root, + deterministic_key_generator, +) + +import pytest +import pytest_twisted + + +@pytest_twisted.inlineCallbacks +def test_retrieve_cap(): + """ + WebUI Fake can serve a read-capability back + """ + + keys = deterministic_key_generator() + root = yield create_fake_tahoe_root() + dummy_readcap = yield root.add_data( + next(keys), + "some dummy content\n"*20 + ) + print("readcap: {}".format(dummy_readcap)) + + assert dummy_readcap.to_string() == "URI:CHK:ifaucqkbifaucqkbifaucqkbie:qg3n5r4q36wvwt33fobghsyqacc5ymnadk7wf6hafamsh6amybza:1:1:380" diff --git a/src/allmydata/testing/__init__.py b/src/allmydata/testing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/allmydata/testing/web.py b/src/allmydata/testing/web.py new file mode 100644 index 000000000..5cf517a54 --- /dev/null +++ b/src/allmydata/testing/web.py @@ -0,0 +1,263 @@ +import io +import os +import string + +import attr + +from twisted.internet import defer +from twisted.python.filepath import ( + FilePath, +) +from twisted.web.resource import ( + Resource, +) +from twisted.web.client import ( + Agent, + FileBodyProducer, +) + +from treq.client import ( + HTTPClient, +) +from treq.testing import ( + RequestTraversalAgent, + RequestSequence, + StubTreq, +) + + +class _FakeTahoeRoot(Resource): + """ + This is a sketch of how an in-memory 'fake' of a Tahoe + WebUI. Ultimately, this will live in Tahoe + """ + + def __init__(self, uri=None): + Resource.__init__(self) # this is an old-style class :( + self._uri = uri + self.putChild(b"uri", self._uri) + + def add_data(self, key, data): + return self._uri.add_data(key, data) + +@attr.s +class _FakeCapability(object): + """ + """ + data=attr.ib() + + +# XXX want to make all kinds of caps, like +# URI:CHK:... URI:DIR2:... etc + +import allmydata.uri +KNOWN_CAPABILITIES = [ + getattr(allmydata.uri, t).BASE_STRING + for t in dir(allmydata.uri) + if hasattr(getattr(allmydata.uri, t), 'BASE_STRING') +] + + +from allmydata.immutable.upload import BaseUploadable +from allmydata.interfaces import IUploadable +from zope.interface import implementer +from twisted.internet.defer import ( + inlineCallbacks, + succeed, + returnValue, +) + + +def deterministic_key_generator(): + character = 0 + while character < (26 * 2): + key = string.letters[character] * 16 + character += 1 + yield key + raise RuntimeError("Ran out of keys") + + +@implementer(IUploadable) +class DataUploadable(BaseUploadable): + # Base gives us: + # set_upload_status + # set_default_encoding_parameters + # get_all_encoding_parameters + + def __init__(self, data, key=None): + self._data = data + self._where = 0 + self._key = key if key is not None else urandom(16) + + def get_encryption_key(self): + return succeed(self._key) + + def get_size(self): + return succeed(len(self._data)) + + @inlineCallbacks + def read(self, amount): + data = [self._data[self._where : self._where + amount]] + self._where += amount + yield + returnValue(data) + + def close(self): + pass + +@inlineCallbacks +def create_fake_capability(kind, key, data): + if kind not in KNOWN_CAPABILITIES: + raise ValueError( + "'{}' not a known kind: {}".format( + kind, + ", ".join(list(KNOWN_CAPABILITIES.keys())), + ) + ) + + # XXX to use a allmydata.immutable.upload.CHKUploader directly, + # we'd need to instantiate: + + from allmydata.immutable.upload import ( + CHKUploader, + EncryptAnUploadable, + ) + from allmydata.immutable.encode import ( + Encoder, + ) + + class _FakeSecretHolder(object): + def get_renewal_secret(self): + return "renewal_secret" + + def get_cancel_secret(self): + return "cancel_secret" + + + @attr.s + class _FakeBucket(object): + data = attr.ib(init=False, default="") + + def callRemoteOnly(self, *args): + pass # print("callRemoteOnly({})".format(args)) + + def callRemote(self, verb, *args): + if verb == 'write': + offset, data = args + assert offset >= len(self.data) + while offset > len(self.data): + self.data += 'X' # marker data; we're padding + self.data += data + elif verb == 'close': + pass + else: + print("callRemote({})".format(args)) + + + @attr.s + class _FakeStorageServer(object): + buckets = attr.ib(default=attr.Factory(lambda: [_FakeBucket()])) + + def get_buckets(self, storage_index): + return succeed(self.buckets) + + def allocate_buckets(self, storage_index, renew_secret, cancel_secret, sharenums, allocated_size, canary=None): + # returns a 2-tuple .. second one maps share-num to BucketWriter + return succeed(( + {}, + { + i: bucket + for i, bucket in enumerate(self.buckets) + } + )) + + class _FakeServer(object): + def get_serverid(self): + return "fake_server" + + def get_name(self): + return "steven" + + def get_version(self): + return { + "http://allmydata.org/tahoe/protocols/storage/v1": { + "maximum-immutable-share-size": 10*1024*1024*1024, + } + } + + def get_lease_seed(self): + return "decafbaddecafbaddeca" + + def get_storage_server(self): + return _FakeStorageServer() + + + class _FakeStorageBroker(object): + def get_servers_for_psi(self, storage_index): + return [_FakeServer()] + + storage_broker = _FakeStorageBroker() + secret_holder = _FakeSecretHolder() + uploader = CHKUploader(storage_broker, secret_holder, progress=None, reactor=None) + uploadable = DataUploadable(data, key=key) + uploadable.set_default_encoding_parameters({ + "n": 1, + "k": 1, + "happy": 1, + }) + encrypted_uploadable = EncryptAnUploadable(uploadable) + + encoder = Encoder() + yield encoder.set_encrypted_uploadable(encrypted_uploadable) + + uploadresults = yield uploader.start(encrypted_uploadable) + + enc_key = yield uploadable.get_encryption_key() + + from allmydata.uri import from_string as uri_from_string + from allmydata.uri import CHKFileURI + + verify_cap = uri_from_string(uploadresults.get_verifycapstr()) + read_cap = CHKFileURI( + enc_key, + verify_cap.uri_extension_hash, + verify_cap.needed_shares, + verify_cap.total_shares, + verify_cap.size, + ) + uploadresults.set_uri(read_cap.to_string()) + + returnValue(read_cap) + + +class _FakeTahoeUriHandler(Resource): + """ + """ + + isLeaf = True + + @inlineCallbacks + def add_data(self, key, data): + """ + adds some data to our grid, returning a capability + """ + cap = yield create_fake_capability("URI:CHK:", key, data) + returnValue(cap) + + def render_GET(self, request): + print(request) + print(request.uri) + return b"URI:DIR2-CHK:some capability" + + +@inlineCallbacks +def create_fake_tahoe_root(): + """ + Probably should take some params to control what this fake does: + return errors, pre-populate capabilities, ... + """ + root = _FakeTahoeRoot( + uri=_FakeTahoeUriHandler(), + ) + yield + returnValue(root) From cf4b3ba008a2ac760c39899570041dfb2bd8e304 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 28 May 2020 13:42:55 -0400 Subject: [PATCH 0148/1054] Correct arguments to super() This was incidentally found when looking at some test failures: super(type, object) is the right invocation, and not the other way around. --- src/allmydata/test/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py index e7a94ff40..a2af857b9 100644 --- a/src/allmydata/test/common.py +++ b/src/allmydata/test/common.py @@ -1269,5 +1269,5 @@ class TrialTestCase(_TrialTestCase): if six.PY2: if isinstance(msg, six.text_type): - return super(self, TrialTestCase).fail(msg.encode("utf8")) - return super(self, TrialTestCase).fail(msg) + return super(TrialTestCase, self).fail(msg.encode("utf8")) + return super(TrialTestCase, self).fail(msg) From b6e00d09f9eee5e6c829b91b3244b6180d239c94 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Thu, 28 May 2020 13:49:13 -0400 Subject: [PATCH 0149/1054] Render self in DirectoryNodeHandler.getChild() We need self.render_POST() etc. to be invoked when we have a request such as "POST /uri/URI:DIR:..."; throwing an error here is probably not the right thing to do. --- src/allmydata/web/directory.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py index 57c222788..2129cec25 100644 --- a/src/allmydata/web/directory.py +++ b/src/allmydata/web/directory.py @@ -103,12 +103,10 @@ class DirectoryNodeHandler(ReplaceMeMixin, Resource, object): # for these nodes, which is that a URI like # "/uri/URI%3ADIR2%3Aj...vq/" (that is, with a trailing slash # or no further children) renders "this" page - name = name.decode('utf8') if not name: - raise EmptyPathnameComponentError( - u"The webapi does not allow empty pathname components", - ) + return self + name = name.decode('utf8') d = self.node.get(name) d.addBoth(self._got_child, req, name) return d From ae7802fc0c5481f51bdc37ec63025191215b30eb Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 29 May 2020 09:24:47 -0400 Subject: [PATCH 0150/1054] Add newsfragment --- newsfragments/3312.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3312.bugfix diff --git a/newsfragments/3312.bugfix b/newsfragments/3312.bugfix new file mode 100644 index 000000000..ed00922a6 --- /dev/null +++ b/newsfragments/3312.bugfix @@ -0,0 +1 @@ +Add your info here \ No newline at end of file From 0633623eccf113a52f719f56db517b2ff7e0808d Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 29 May 2020 19:47:12 -0400 Subject: [PATCH 0151/1054] Reject URIs that contain empty segments --- src/allmydata/web/directory.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py index 2129cec25..fced8c68e 100644 --- a/src/allmydata/web/directory.py +++ b/src/allmydata/web/directory.py @@ -106,6 +106,16 @@ class DirectoryNodeHandler(ReplaceMeMixin, Resource, object): if not name: return self + # Rejecting URIs that contain empty path pieces (for example: + # "/uri/URI:DIR2:../foo//new.txt" or "/uri/URI:DIR2:..//") was + # the old nevow behavior and it is encoded in the test suite; + # we will follow suit. + for segment in req.prepath: + if not segment: + raise EmptyPathnameComponentError( + u"The webapi does not allow empty pathname components", + ) + name = name.decode('utf8') d = self.node.get(name) d.addBoth(self._got_child, req, name) From 31eba00d0fef88d6ba2aad6b28cadda94ff50bd1 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 29 May 2020 21:38:17 -0400 Subject: [PATCH 0152/1054] Reject trailing empty path piece in URIs --- src/allmydata/web/directory.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py index fced8c68e..b1618a3f5 100644 --- a/src/allmydata/web/directory.py +++ b/src/allmydata/web/directory.py @@ -102,8 +102,9 @@ class DirectoryNodeHandler(ReplaceMeMixin, Resource, object): # trying to replicate what I have observed as Nevow behavior # for these nodes, which is that a URI like # "/uri/URI%3ADIR2%3Aj...vq/" (that is, with a trailing slash - # or no further children) renders "this" page - if not name: + # or no further children) renders "this" page. We also need + # to reject "/uri/URI:DIR2:..//", so we look at postpath. + if not name and req.postpath != ['']: return self # Rejecting URIs that contain empty path pieces (for example: From dce73f7c2fd4c304782342a1bb51645b62a87a53 Mon Sep 17 00:00:00 2001 From: Sajith Sasidharan Date: Fri, 29 May 2020 21:38:35 -0400 Subject: [PATCH 0153/1054] Make "Refresh" link work This seems to be a subtle difference from nevow: with `href="."`, rendered link target will be `/uri/`, so clicking "Refresh" will result in an error message like so: "GET /uri requires uri=". With `href=""`, the rendered link target will be `/uri/URI:...`, which is what we need. --- src/allmydata/web/directory.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/web/directory.xhtml b/src/allmydata/web/directory.xhtml index 88058efa4..eeb04692d 100644 --- a/src/allmydata/web/directory.xhtml +++ b/src/allmydata/web/directory.xhtml @@ -31,7 +31,7 @@