From d004d91883cc511430c424cda6c0183cdc5021e0 Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Mon, 6 Dec 2021 10:27:24 -0500
Subject: [PATCH] add metric for add_lease operation

---
 src/_zkapauthorizer/_storage_server.py        | 26 +++++--
 .../tests/test_storage_server.py              | 68 +++++++++++++++++--
 2 files changed, 85 insertions(+), 9 deletions(-)

diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py
index a0de104..50e1920 100644
--- a/src/_zkapauthorizer/_storage_server.py
+++ b/src/_zkapauthorizer/_storage_server.py
@@ -365,12 +365,27 @@ class ZKAPAuthorizerStorageServer(Referenceable):
             passes,
             self._signing_key,
         )
-        check_pass_quantity_for_lease(
+        allocated_sizes = check_pass_quantity_for_lease(
             self._pass_value,
             storage_index,
             validation,
             self._original,
         )
+        # siiiigh.  Tahoe doesn't guarantee that mutable shares in a single
+        # slot are all the same size.  They *probably* are.  The official
+        # (only) Tahoe client always makes them the same size.  The storage
+        # protocol allows a client to make them different sizes though.
+        #
+        # So ... deal with that here.  Attribute the ZKAPs being spent
+        # proportionally to the size of each share.
+        observe_spending_successes(
+            self._metric_spending_successes,
+            compute_spending_metrics(
+                self._pass_value,
+                allocated_sizes.values(),
+            ),
+        )
+
         return self._original.remote_add_lease(storage_index, *a, **kw)
 
     def remote_advise_corrupt_share(self, *a, **kw):
@@ -534,6 +549,7 @@ def check_pass_quantity(pass_value, validation, share_sizes):
 def check_pass_quantity_for_lease(
     pass_value, storage_index, validation, storage_server
 ):
+    # type: (int, bytes, _ValidationResult, ZKAPAuthorizerStorageServer) -> Dict[int, int]
     """
     Check that the given number of passes is sufficient to add or renew a
     lease for one period for the given storage index.
@@ -545,7 +561,8 @@ def check_pass_quantity_for_lease(
     :raise MorePassesRequired: If the given number of passes is too few for
         the share sizes at the given storage index.
 
-    :return: ``None`` if the given number of passes is sufficient.
+    :return: A mapping from share number to share size on the server if the
+        number of passes given is sufficient.
     """
     allocated_sizes = dict(
         get_share_sizes(
@@ -553,8 +570,9 @@ def check_pass_quantity_for_lease(
             storage_index,
             list(get_all_share_numbers(storage_server, storage_index)),
         ),
-    ).values()
-    check_pass_quantity(pass_value, validation, allocated_sizes)
+    )
+    check_pass_quantity(pass_value, validation, allocated_sizes.values())
+    return allocated_sizes
 
 
 def check_pass_quantity_for_write(pass_value, validation, sharenums, allocated_size):
diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py
index 49e6d60..e20da2a 100644
--- a/src/_zkapauthorizer/tests/test_storage_server.py
+++ b/src/_zkapauthorizer/tests/test_storage_server.py
@@ -25,9 +25,9 @@ from prometheus_client import CollectorRegistry, Histogram
 from challenge_bypass_ristretto import RandomToken, random_signing_key
 from foolscap.referenceable import LocalReferenceable
 from hypothesis import given, note
-from hypothesis.strategies import integers, just, lists, one_of, tuples
-from testtools import TestCase
-from testtools.matchers import AfterPreprocessing, Equals, MatchesAll
+from hypothesis.strategies import integers, just, lists, one_of, tuples, dictionaries
+from testtools import TestCase, skip
+from testtools.matchers import AfterPreprocessing, Not, Equals, MatchesAll, MatchesPredicate, AllMatch
 from twisted.internet.task import Clock
 from twisted.python.runtime import platform
 
@@ -51,6 +51,7 @@ from .strategies import (
     lease_cancel_secrets,
     lease_renew_secrets,
     sharenum_sets,
+    sharenums,
     sizes,
     slot_test_and_write_vectors_for_shares,
     storage_indexes,
@@ -150,8 +151,9 @@ def read_bucket(storage_server, size):
         if size <= upper_bound:
             break
 
+    note("bucket_number {}".format(bucket_number))
     buckets = storage_server._metric_spending_successes._buckets
-    note(list((n, b.get()) for n, b in enumerate(buckets)))
+    note("bucket counters: {}".format(list((n, b.get()) for n, b in enumerate(buckets))))
     return buckets[bucket_number].get()
 
 
@@ -676,7 +678,63 @@ class PassValidationTests(TestCase):
         after_count = read_count(self.storage_server)
         after_bucket = read_bucket(self.storage_server, size)
 
-        note("bucket_number {}".format(bucket_number))
+        self.expectThat(
+            after_count - before_count,
+            Equals(expected),
+            "Unexpected histogram sum value",
+        )
+        self.assertThat(
+            after_bucket - before_bucket,
+            Equals(expected),
+            "Unexpected histogram bucket value",
+        )
+
+    @given(
+        storage_index=storage_indexes(),
+        renew_secret=lease_renew_secrets(),
+        cancel_secret=lease_cancel_secrets(),
+        sharenums=sharenum_sets(),
+        allocated_size=sizes(),
+    )
+    def test_add_lease_metrics(
+        self, storage_index, renew_secret, cancel_secret, sharenums, allocated_size,
+    ):
+        # Create some shares at a slot which will require lease renewal.
+        write_toy_shares(
+            self.anonymous_storage_server,
+            storage_index,
+            renew_secret,
+            cancel_secret,
+            sharenums,
+            allocated_size,
+            LocalReferenceable(None),
+        )
+
+        expected = required_passes(
+            self.storage_server._pass_value, [allocated_size] * len(sharenums)
+        )
+        valid_passes = make_passes(
+            self.signing_key,
+            add_lease_message(storage_index),
+            list(RandomToken.create() for i in range(expected)),
+        )
+
+        before_count = read_count(self.storage_server)
+        before_bucket = read_bucket(self.storage_server, allocated_size)
+
+        self.storage_server.doRemoteCall(
+            "add_lease",
+            (),
+            dict(
+                passes=valid_passes,
+                storage_index=storage_index,
+                renew_secret=renew_secret,
+                cancel_secret=cancel_secret,
+            ),
+        )
+
+        after_count = read_count(self.storage_server)
+        after_bucket = read_bucket(self.storage_server, allocated_size)
 
         self.expectThat(
             after_count - before_count,
-- 
GitLab