commit 80dc4719da78527458a97ab33b97b1156e67f113
Author: Arturo Filastò <art(a)fuffa.org>
Date: Wed Jun 25 13:18:05 2014 +0200
Add tool for viewing the status of report submission and allow users to upload reports.
---
bin/oonireport | 26 +++++++++++++++++
ooni/report/__init__.py | 1 +
ooni/report/cli.py | 57 ++++++++++++++++++++++++++++++++++++
ooni/report/parser.py | 35 +++++++++++++++++++++++
ooni/report/tool.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 192 insertions(+)
diff --git a/bin/oonireport b/bin/oonireport
new file mode 100755
index 0000000..4e6d48a
--- /dev/null
+++ b/bin/oonireport
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+import os
+import sys
+
+sys.path[:] = map(os.path.abspath, sys.path)
+sys.path.insert(0, os.path.abspath(os.getcwd()))
+
+from twisted.internet import defer, reactor
+
+from ooni.report import cli
+
+def failed(failure):
+ log.err("Failed to run ooni-report")
+ log.exception(failure)
+ reactor.stop()
+
+def done(result):
+ reactor.stop()
+
+def start():
+ d = defer.maybeDeferred(cli.run)
+ d.addCallback(done)
+ d.addErrback(done)
+
+reactor.callWhenRunning(start)
+reactor.run()
diff --git a/ooni/report/__init__.py b/ooni/report/__init__.py
new file mode 100644
index 0000000..a4e2017
--- /dev/null
+++ b/ooni/report/__init__.py
@@ -0,0 +1 @@
+__version__ = "0.1"
diff --git a/ooni/report/cli.py b/ooni/report/cli.py
new file mode 100644
index 0000000..eee5c53
--- /dev/null
+++ b/ooni/report/cli.py
@@ -0,0 +1,57 @@
+import os
+import sys
+
+from ooni.report import __version__
+from ooni.report import tool
+from ooni.settings import config
+
+from twisted.python import usage
+
+
+class Options(usage.Options):
+
+ synopsis = """%s [options] upload | status
+""" % (os.path.basename(sys.argv[0]),)
+
+ optParameters = [
+ ["collector", "c", None,
+ "Specify the collector to upload the result to."],
+ ["bouncer", "b", None,
+ "Specify the bouncer to query for a collector."]
+ ]
+
+ def opt_version(self):
+ print "oonireport version:", __version__
+ sys.exit(0)
+
+ def parseArgs(self, *args):
+ self['command'] = args[0]
+ if self['command'] not in ("upload", "status"):
+ raise usage.UsageError(
+ "Must specify either command upload or status"
+ )
+ if self['command'] == "upload":
+ try:
+ self['report_file'] = args[1]
+ except KeyError:
+ self['report_file'] = None
+
+
+def parse_options():
+ options = Options()
+ options.parseOptions()
+ return dict(options)
+
+
+def run():
+ config.read_config_file()
+ options = parse_options()
+ if options['command'] == "upload" and options['report_file']:
+ return tool.upload(options['report_file'],
+ options['collector'],
+ options['bouncer'])
+ elif options['command'] == "upload":
+ return tool.upload_all(options['collector'],
+ options['bouncer'])
+ elif options['command'] == "status":
+ return tool.status()
diff --git a/ooni/report/parser.py b/ooni/report/parser.py
new file mode 100644
index 0000000..a986522
--- /dev/null
+++ b/ooni/report/parser.py
@@ -0,0 +1,35 @@
+import yaml
+
+
+class ReportLoader(object):
+ _header_keys = (
+ 'probe_asn',
+ 'probe_cc',
+ 'probe_ip',
+ 'start_time',
+ 'test_name',
+ 'test_version',
+ 'options',
+ 'input_hashes',
+ 'software_name',
+ 'software_version'
+ )
+
+ def __init__(self, report_filename):
+ self._fp = open(report_filename)
+ self._yfp = yaml.safe_load_all(self._fp)
+
+ self.header = self._yfp.next()
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ try:
+ self._yfp.next()
+ except StopIteration:
+ self.close()
+ raise StopIteration
+
+ def close(self):
+ self._fp.close()
diff --git a/ooni/report/tool.py b/ooni/report/tool.py
new file mode 100644
index 0000000..f3dd430
--- /dev/null
+++ b/ooni/report/tool.py
@@ -0,0 +1,73 @@
+import yaml
+
+from twisted.internet import defer
+
+from ooni.reporter import OONIBReporter, OONIBReportLog
+
+from ooni.utils import log
+from ooni.report import parser
+from ooni.settings import config
+from ooni.oonibclient import OONIBClient
+
+
+oonib_report_log = OONIBReportLog()
+
+
+(a)defer.inlineCallbacks
+def upload(report_file, collector=None, bouncer=None):
+ with open(config.report_log_file) as f:
+ report_log = yaml.safe_load(f)
+
+ report = parser.ReportLoader(report_file)
+ if bouncer:
+ oonib_client = OONIBClient(bouncer)
+ collector = yield oonib_client.lookupTestCollector(
+ report.header['test_name']
+ )
+
+ if collector is None:
+ try:
+ collector = report_log[report_file]['collector']
+ except KeyError:
+ raise Exception(
+ "No collector or bouncer specified and report file not in log."
+ )
+
+ oonib_reporter = OONIBReporter(report.header, collector)
+ log.msg("Creating report for %s with %s" % (report_file, collector))
+ report_id = yield oonib_reporter.createReport()
+ yield oonib_report_log.created(report_file, collector, report_id)
+ for entry in report:
+ print "Writing entry"
+ yield oonib_reporter.writeReportEntry(entry)
+ log.msg("Closing report.")
+ yield oonib_reporter.finish()
+ yield oonib_report_log.closed(report_file)
+
+
+(a)defer.inlineCallbacks
+def upload_all(collector=None, bouncer=None):
+ for report_file, value in oonib_report_log.reports_to_upload:
+ yield upload(report_file, collector, bouncer)
+
+
+def print_report(report_file, value):
+ print "* %s" % report_file
+ print " %s" % value['created_at']
+
+
+def status():
+ print "Reports to be uploaded"
+ print "----------------------"
+ for report_file, value in oonib_report_log.reports_to_upload:
+ print_report(report_file, value)
+
+ print "Reports in progress"
+ print "-------------------"
+ for report_file, value in oonib_report_log.reports_in_progress:
+ print_report(report_file, value)
+
+ print "Incomplete reports"
+ print "------------------"
+ for report_file, value in oonib_report_log.reports_incomplete:
+ print_report(report_file, value)