From fe1df149e1b05308860e3c3fff7ecf2e7a626172 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Wed, 15 Feb 2012 18:24:42 +0000
Subject: [PATCH] make provisioning/reliability work in the new location, fix
 tests

---
 Makefile                                      |   4 +
 .../provisioning/provisioning.py              |  22 ++-
 misc/operations_helpers/provisioning/run.py   |  45 +++++
 .../operations_helpers/provisioning/tahoe.css | 163 ++++++++++++++++++
 .../provisioning/test_provisioning.py         |   4 +-
 misc/operations_helpers/provisioning/util.py  |   5 +
 .../provisioning/web_reliability.py           |  32 +++-
 7 files changed, 257 insertions(+), 18 deletions(-)
 create mode 100644 misc/operations_helpers/provisioning/run.py
 create mode 100644 misc/operations_helpers/provisioning/tahoe.css
 create mode 100644 misc/operations_helpers/provisioning/util.py

diff --git a/Makefile b/Makefile
index 38bb6479..4bf09d39 100644
--- a/Makefile
+++ b/Makefile
@@ -199,6 +199,10 @@ check-grid: .built
 bench-dirnode: .built
 	$(TAHOE) @src/allmydata/test/bench_dirnode.py
 
+# the provisioning tool runs as a stand-alone webapp server
+run-provisioning-tool: .built
+	$(TAHOE) @misc/operations_helpers/provisioning/run.py
+
 # 'make repl' is a simple-to-type command to get a Python interpreter loop
 # from which you can type 'import allmydata'
 repl:
diff --git a/misc/operations_helpers/provisioning/provisioning.py b/misc/operations_helpers/provisioning/provisioning.py
index 9d9af0ea..37acd16d 100644
--- a/misc/operations_helpers/provisioning/provisioning.py
+++ b/misc/operations_helpers/provisioning/provisioning.py
@@ -1,12 +1,17 @@
 
-from nevow import inevow, rend, tags as T
+from nevow import inevow, rend, loaders, tags as T
 import math
-from allmydata.util import mathutil
-from allmydata.web.common import getxmlfile
+import util
 
 # factorial and binomial copied from
 # http://mail.python.org/pipermail/python-list/2007-April/435718.html
 
+def div_ceil(n, d):
+    """
+    The smallest integer k such that k*d >= n.
+    """
+    return (n/d) + (n%d != 0)
+
 def factorial(n):
     """factorial(n): return the factorial of the integer n.
     factorial(0) = 1
@@ -35,7 +40,7 @@ def binomial(n, k):
 
 class ProvisioningTool(rend.Page):
     addSlash = True
-    docFactory = getxmlfile("provisioning.xhtml")
+    docFactory = loaders.xmlfile(util.sibling("provisioning.xhtml"))
 
     def render_forms(self, ctx, data):
         req = inevow.IRequest(ctx)
@@ -566,12 +571,12 @@ class ProvisioningTool(rend.Page):
                              number(total_file_check_rate,
                                     "Hz")])
 
-            total_drives = max(mathutil.div_ceil(int(total_share_space),
-                                                 int(drive_size)),
+            total_drives = max(div_ceil(int(total_share_space),
+                                        int(drive_size)),
                                num_servers)
             add_output("Drives",
                        T.div["Total drives: ", number(total_drives), " drives"])
-            drives_per_server = mathutil.div_ceil(total_drives, num_servers)
+            drives_per_server = div_ceil(total_drives, num_servers)
             add_output("Servers",
                        T.div["Drives per server: ", drives_per_server])
 
@@ -606,8 +611,7 @@ class ProvisioningTool(rend.Page):
             # $44/server/mo power+space
             server_bandwidth = max(server_inbound_byte_rate,
                                    server_outbound_byte_rate)
-            server_bandwidth_mbps = mathutil.div_ceil(int(server_bandwidth*8),
-                                                      int(1e6))
+            server_bandwidth_mbps = div_ceil(int(server_bandwidth*8), int(1e6))
             server_monthly_cost = 70*server_bandwidth_mbps + 44
             add_output("Servers", T.div["Monthly cost per server: $",
                                         server_monthly_cost])
diff --git a/misc/operations_helpers/provisioning/run.py b/misc/operations_helpers/provisioning/run.py
new file mode 100644
index 00000000..d81f771a
--- /dev/null
+++ b/misc/operations_helpers/provisioning/run.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+# this depends upon Twisted and Nevow, but not upon Tahoe itself
+
+import webbrowser
+
+from twisted.application import strports
+from twisted.internet import reactor
+from nevow import appserver, rend, loaders
+from twisted.web import static
+import web_reliability, provisioning
+
+class Root(rend.Page):
+    docFactory = loaders.xmlstr('''\
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+  <head>
+    <title>Tahoe-LAFS Provisioning/Reliability Calculator</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  </head>
+  <body>
+  <p><a href="reliability">Reliability Tool</a></p>
+  <p><a href="provisioning">Provisioning Tool</a></p>
+  </body>
+</html>
+''')
+
+    child_reliability = web_reliability.ReliabilityTool()
+    child_provisioning = provisioning.ProvisioningTool()
+
+
+def run(portnum):
+    root = Root()
+    root.putChild("tahoe.css", static.File("tahoe.css"))
+    site = appserver.NevowSite(root)
+    s = strports.service("tcp:%d" % portnum, site)
+    s.startService()
+    reactor.callLater(1.0, webbrowser.open, "http://localhost:%d/" % portnum)
+    reactor.run()
+
+if __name__ == '__main__':
+    import sys
+    portnum = 8070
+    if len(sys.argv) > 1:
+        portnum = int(sys.argv[1])
+    run(portnum)
diff --git a/misc/operations_helpers/provisioning/tahoe.css b/misc/operations_helpers/provisioning/tahoe.css
new file mode 100644
index 00000000..e834ecc7
--- /dev/null
+++ b/misc/operations_helpers/provisioning/tahoe.css
@@ -0,0 +1,163 @@
+
+pre.overflow {
+               background: #f7f7f7;
+               border: 1px solid #d7d7d7;
+               margin: 1em 1.75em;
+               padding: .25em;
+               overflow: auto;
+               }
+               
+/* ----------------------------------------------------------------------- */
+
+/* colors borrowed from the Allmydata logo */
+
+/* general style */
+h1 {
+  text-align: center;
+}
+table { 
+  margin: 1em auto;
+  border: .2em solid #3289b4; 
+  border-spacing: 1px;
+}
+th { 
+  color: white;
+  background-color: #58a1c3;
+}
+td {
+    padding: .3em .3em;
+}
+
+th {
+    padding: .3em .3em;
+}
+
+.table-headings-top th {
+    text-align: center;
+    
+}
+.table-headings-left th {
+    text-align: right;
+    vertical-align: top;
+}
+legend {
+  font-weight: bold;
+}
+
+.connected-yes, .connected-True {
+  border: 1px solid #75d24a;
+  background-color: #EFE;
+}
+.connected-no, .connected-False {
+  border: 1px solid #F00;
+  background-color: #FBB;
+}
+
+.encoded, .nodeid {
+  font-family: monospace;
+  font-size: 80%;
+}
+
+.empty-marker {
+  background-color: white;
+  color: gray;
+}
+table td.empty-marker {
+  padding: 6em 10em;
+  text-align: center;
+  vertical-align: center;
+}
+
+/* styles for server listings in tables (nickname above nodeid) */
+th.nickname-and-peerid {
+  text-align: left;
+}
+.nickname {
+  font: inherit;
+  font-family: sans-serif;
+  font-weight: bold;
+}
+
+
+/* just in case, make sure floats don't stomp on big tables etc. */
+#section { clear: both; }
+
+/* section-specific styles - turn this client info into a sidebar */
+#this-client {
+  font-size: 60%;
+  border: .2em solid #3289b4;
+  float: right;
+  width: 40%;
+  margin: 0 0 .5em .5em;
+  padding: 3px;
+}
+#this-client .nodeid { font-size: inherit; }
+#this-client h2 {
+  text-align: center;
+  background: #3289b4;
+  color: white;
+  margin: -2px -2px 0 -2px; /* matches padding */
+  padding: .3em;
+}
+#this-client table { 
+  font-size: inherit;
+  margin: 0 -3px -3px -3px; /* matches padding */
+}
+#this-client td > ul {
+  list-style-type: outside;
+  margin: 0 0 0 2.3em;
+  padding-left: 0;
+}
+
+
+/* services table */
+.services {
+}
+
+/* --- Directory page styles --- */
+
+body.tahoe-directory-page {
+  color: black;
+  background: #c0d9e6;
+  margin: 1em 0; /* zero margin so the table can be flush */
+}
+table.tahoe-directory {
+  color: black;
+  background: white;
+  width: 100%;
+  /*border-left-color: #D7E0E5;
+  border-right-color: #D7E0E5;*/
+  border-left: 0;
+  border-right: 0;
+}
+.tahoe-directory-footer {
+  color: black;
+  background: #c0d9e6;
+  margin: 0 1em; /* compensate for page 0 margin */
+}
+
+/* directory-screen toolbar */
+.toolbar {
+  display: table;
+  margin: .2em auto;
+  text-align: center;
+  /*width: 100%;*/
+}
+.toolbar .toolbar-item {
+  display: inline;
+  text-align: center;
+  padding: 0 1em;
+}
+
+/* recent upload/download status pages */
+
+table.status-download-events {
+  #border: 1px solid #aaa;
+  margin: 1em auto;
+  border: .2em solid #3289b4; 
+  border-spacing: 1px;
+}
+table.status-download-events td {
+  border: 1px solid #a00;
+  padding: 2px
+}
diff --git a/misc/operations_helpers/provisioning/test_provisioning.py b/misc/operations_helpers/provisioning/test_provisioning.py
index 71bc6570..d2b9dbd1 100644
--- a/misc/operations_helpers/provisioning/test_provisioning.py
+++ b/misc/operations_helpers/provisioning/test_provisioning.py
@@ -1,5 +1,5 @@
 
-from twisted.trial import unittest
+import unittest
 from allmydata import provisioning
 ReliabilityModel = None
 try:
@@ -111,3 +111,5 @@ class Reliability(unittest.TestCase):
         self.failUnlessAlmostEqual(P_dead_unmaintained, 0.033591004555395272)
         self.failUnlessAlmostEqual(P_dead_maintained, 3.2983995819177542e-08)
 
+if __name__=='__main__':
+    unittest.main()
diff --git a/misc/operations_helpers/provisioning/util.py b/misc/operations_helpers/provisioning/util.py
new file mode 100644
index 00000000..db28915c
--- /dev/null
+++ b/misc/operations_helpers/provisioning/util.py
@@ -0,0 +1,5 @@
+
+import os.path
+
+def sibling(filename):
+    return os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
diff --git a/misc/operations_helpers/provisioning/web_reliability.py b/misc/operations_helpers/provisioning/web_reliability.py
index d5d34061..149079e9 100644
--- a/misc/operations_helpers/provisioning/web_reliability.py
+++ b/misc/operations_helpers/provisioning/web_reliability.py
@@ -1,11 +1,27 @@
 
-from nevow import rend, tags as T
-reliability = None # might not be usable
-try:
-    from allmydata import reliability # requires NumPy
-except ImportError:
-    pass
-from allmydata.web.common import getxmlfile, get_arg
+from nevow import rend, loaders, tags as T
+from nevow.inevow import IRequest
+import reliability # requires NumPy
+import util
+
+def get_arg(ctx_or_req, argname, default=None, multiple=False):
+    """Extract an argument from either the query args (req.args) or the form
+    body fields (req.fields). If multiple=False, this returns a single value
+    (or the default, which defaults to None), and the query args take
+    precedence. If multiple=True, this returns a tuple of arguments (possibly
+    empty), starting with all those in the query args.
+    """
+    req = IRequest(ctx_or_req)
+    results = []
+    if argname in req.args:
+        results.extend(req.args[argname])
+    if req.fields and argname in req.fields:
+        results.append(req.fields[argname].value)
+    if multiple:
+        return tuple(results)
+    if results:
+        return results[0]
+    return default
 
 
 DAY=24*60*60
@@ -22,7 +38,7 @@ def yandm(seconds):
 
 class ReliabilityTool(rend.Page):
     addSlash = True
-    docFactory = getxmlfile("reliability.xhtml")
+    docFactory = loaders.xmlfile(util.sibling("reliability.xhtml"))
 
     DEFAULT_PARAMETERS = [
         ("drive_lifetime", "8Y", "time",
-- 
2.45.2