remove unused files
This server won't use a database, just a logfile and statsfile. And it doesn't have a CLI command, just a twist/twistd plugin.
This commit is contained in:
		
							parent
							
								
									0dc3fd5e7e
								
							
						
					
					
						commit
						c287175d38
					
				|  | @ -1,156 +0,0 @@ | ||||||
| from __future__ import print_function |  | ||||||
| import json |  | ||||||
| import click |  | ||||||
| from ..cli.cli import Config, _compose |  | ||||||
| 
 |  | ||||||
| # can put this back in to get this command as "wormhole server" |  | ||||||
| # instead |  | ||||||
| #from ..cli.cli import wormhole |  | ||||||
| #@wormhole.group() |  | ||||||
| @click.group() |  | ||||||
| @click.pass_context |  | ||||||
| def server(ctx): # this is the setuptools entrypoint for bin/wormhole-server |  | ||||||
|     """ |  | ||||||
|     Control a relay server (most users shouldn't need to worry |  | ||||||
|     about this and can use the default server). |  | ||||||
|     """ |  | ||||||
|     # just leaving this pointing to wormhole.cli.cli.Config for now, |  | ||||||
|     # but if we want to keep wormhole-server as a separate command |  | ||||||
|     # should probably have our own Config without all the options the |  | ||||||
|     # server commands don't use |  | ||||||
|     ctx.obj = Config() |  | ||||||
| 
 |  | ||||||
| def _validate_websocket_protocol_options(ctx, param, value): |  | ||||||
|     return list(_validate_websocket_protocol_option(option) for option in value) |  | ||||||
| 
 |  | ||||||
| def _validate_websocket_protocol_option(option): |  | ||||||
|     try: |  | ||||||
|         key, value = option.split("=", 1) |  | ||||||
|     except ValueError: |  | ||||||
|         raise click.BadParameter("format options as OPTION=VALUE") |  | ||||||
| 
 |  | ||||||
|     try: |  | ||||||
|         value = json.loads(value) |  | ||||||
|     except: |  | ||||||
|         raise click.BadParameter("could not parse JSON value for {}".format(key)) |  | ||||||
| 
 |  | ||||||
|     return (key, value) |  | ||||||
| 
 |  | ||||||
| LaunchArgs = _compose( |  | ||||||
|     click.option( |  | ||||||
|         "--rendezvous", default="tcp:4000", metavar="tcp:PORT", |  | ||||||
|         help="endpoint specification for the rendezvous port", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--transit", default="tcp:4001", metavar="tcp:PORT", |  | ||||||
|         help="endpoint specification for the transit-relay port", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--advertise-version", metavar="VERSION", |  | ||||||
|         help="version to recommend to clients", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--blur-usage", default=None, type=int, |  | ||||||
|         metavar="SECONDS", |  | ||||||
|         help="round logged access times to improve privacy", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--no-daemon", "-n", is_flag=True, |  | ||||||
|         help="Run in the foreground", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--signal-error", is_flag=True, |  | ||||||
|         help="force all clients to fail with a message", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--allow-list/--disallow-list", default=True, |  | ||||||
|         help="always/never send list of allocated nameplates", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--relay-database-path", default="relay.sqlite", metavar="PATH", |  | ||||||
|         help="location for the relay server state database", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--stats-json-path", default="stats.json", metavar="PATH", |  | ||||||
|         help="location to write the relay stats file", |  | ||||||
|     ), |  | ||||||
|     click.option( |  | ||||||
|         "--websocket-protocol-option", multiple=True, metavar="OPTION=VALUE", |  | ||||||
|         callback=_validate_websocket_protocol_options, |  | ||||||
|         help="a websocket server protocol option to configure", |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @server.command() |  | ||||||
| @LaunchArgs |  | ||||||
| @click.pass_obj |  | ||||||
| def start(cfg, **kwargs): |  | ||||||
|     """ |  | ||||||
|     Start a relay server |  | ||||||
|     """ |  | ||||||
|     for name, value in kwargs.items(): |  | ||||||
|         setattr(cfg, name, value) |  | ||||||
|     from wormhole.server.cmd_server import start_server |  | ||||||
|     start_server(cfg) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @server.command() |  | ||||||
| @LaunchArgs |  | ||||||
| @click.pass_obj |  | ||||||
| def restart(cfg, **kwargs): |  | ||||||
|     """ |  | ||||||
|     Re-start a relay server |  | ||||||
|     """ |  | ||||||
|     for name, value in kwargs.items(): |  | ||||||
|         setattr(cfg, name, value) |  | ||||||
|     from wormhole.server.cmd_server import restart_server |  | ||||||
|     restart_server(cfg) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @server.command() |  | ||||||
| @click.pass_obj |  | ||||||
| def stop(cfg): |  | ||||||
|     """ |  | ||||||
|     Stop a relay server |  | ||||||
|     """ |  | ||||||
|     from wormhole.server.cmd_server import stop_server |  | ||||||
|     stop_server(cfg) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @server.command(name="tail-usage") |  | ||||||
| @click.pass_obj |  | ||||||
| def tail_usage(cfg): |  | ||||||
|     """ |  | ||||||
|     Follow the latest usage |  | ||||||
|     """ |  | ||||||
|     from wormhole.server.cmd_usage import tail_usage |  | ||||||
|     tail_usage(cfg) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @server.command(name='count-channels') |  | ||||||
| @click.option( |  | ||||||
|     "--json", is_flag=True, |  | ||||||
| ) |  | ||||||
| @click.pass_obj |  | ||||||
| def count_channels(cfg, json): |  | ||||||
|     """ |  | ||||||
|     Count active channels |  | ||||||
|     """ |  | ||||||
|     from wormhole.server.cmd_usage import count_channels |  | ||||||
|     cfg.json = json |  | ||||||
|     count_channels(cfg) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @server.command(name='count-events') |  | ||||||
| @click.option( |  | ||||||
|     "--json", is_flag=True, |  | ||||||
| ) |  | ||||||
| @click.pass_obj |  | ||||||
| def count_events(cfg, json): |  | ||||||
|     """ |  | ||||||
|     Count events |  | ||||||
|     """ |  | ||||||
|     from wormhole.server.cmd_usage import count_events |  | ||||||
|     cfg.json = json |  | ||||||
|     count_events(cfg) |  | ||||||
|  | @ -1,73 +0,0 @@ | ||||||
| from __future__ import print_function, unicode_literals |  | ||||||
| import os, time |  | ||||||
| from twisted.python import usage |  | ||||||
| from twisted.scripts import twistd |  | ||||||
| 
 |  | ||||||
| class MyPlugin(object): |  | ||||||
|     tapname = "xyznode" |  | ||||||
| 
 |  | ||||||
|     def __init__(self, args): |  | ||||||
|         self.args = args |  | ||||||
| 
 |  | ||||||
|     def makeService(self, so): |  | ||||||
|         # delay this import as late as possible, to allow twistd's code to |  | ||||||
|         # accept --reactor= selection |  | ||||||
|         from .server import RelayServer |  | ||||||
|         return RelayServer( |  | ||||||
|             str(self.args.rendezvous), |  | ||||||
|             str(self.args.transit), |  | ||||||
|             self.args.advertise_version, |  | ||||||
|             self.args.relay_database_path, |  | ||||||
|             self.args.blur_usage, |  | ||||||
|             signal_error=self.args.signal_error, |  | ||||||
|             stats_file=self.args.stats_json_path, |  | ||||||
|             allow_list=self.args.allow_list, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
| class MyTwistdConfig(twistd.ServerOptions): |  | ||||||
|     subCommands = [("XYZ", None, usage.Options, "node")] |  | ||||||
| 
 |  | ||||||
| def start_server(args): |  | ||||||
|     c = MyTwistdConfig() |  | ||||||
|     #twistd_args = tuple(args.twistd_args) + ("XYZ",) |  | ||||||
|     base_args = [] |  | ||||||
|     if args.no_daemon: |  | ||||||
|         base_args.append("--nodaemon") |  | ||||||
|     twistd_args = base_args + ["XYZ"] |  | ||||||
|     c.parseOptions(tuple(twistd_args)) |  | ||||||
|     c.loadedPlugins = {"XYZ": MyPlugin(args)} |  | ||||||
| 
 |  | ||||||
|     print("starting wormhole relay server") |  | ||||||
|     # this forks and never comes back. The parent calls os._exit(0) |  | ||||||
|     twistd.runApp(c) |  | ||||||
| 
 |  | ||||||
| def kill_server(): |  | ||||||
|     try: |  | ||||||
|         f = open("twistd.pid", "r") |  | ||||||
|     except EnvironmentError: |  | ||||||
|         print("Unable to find twistd.pid: is this really a server directory?") |  | ||||||
|         print("oh well, ignoring 'stop'") |  | ||||||
|         return |  | ||||||
|     pid = int(f.read().strip()) |  | ||||||
|     f.close() |  | ||||||
|     os.kill(pid, 15) |  | ||||||
|     print("server process %d sent SIGTERM" % pid) |  | ||||||
|     return |  | ||||||
| 
 |  | ||||||
| def stop_server(args): |  | ||||||
|     kill_server() |  | ||||||
| 
 |  | ||||||
| def restart_server(args): |  | ||||||
|     kill_server() |  | ||||||
|     time.sleep(0.1) |  | ||||||
|     timeout = 0 |  | ||||||
|     while os.path.exists("twistd.pid") and timeout < 10: |  | ||||||
|         if timeout == 0: |  | ||||||
|             print(" waiting for shutdown..") |  | ||||||
|         timeout += 1 |  | ||||||
|         time.sleep(1) |  | ||||||
|     if os.path.exists("twistd.pid"): |  | ||||||
|         print("error: unable to shut down old server") |  | ||||||
|         return 1 |  | ||||||
|     print(" old server shut down") |  | ||||||
|     start_server(args) |  | ||||||
|  | @ -1,226 +0,0 @@ | ||||||
| from __future__ import print_function, unicode_literals |  | ||||||
| import os, time, json |  | ||||||
| from collections import defaultdict |  | ||||||
| import click |  | ||||||
| from humanize import naturalsize |  | ||||||
| from .database import get_db |  | ||||||
| 
 |  | ||||||
| def abbrev(t): |  | ||||||
|     if t is None: |  | ||||||
|         return "-" |  | ||||||
|     if t > 1.0: |  | ||||||
|         return "%.3fs" % t |  | ||||||
|     if t > 1e-3: |  | ||||||
|         return "%.1fms" % (t*1e3) |  | ||||||
|     return "%.1fus" % (t*1e6) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def print_event(event): |  | ||||||
|     event_type, started, result, total_bytes, waiting_time, total_time = event |  | ||||||
|     followthrough = None |  | ||||||
|     if waiting_time and total_time: |  | ||||||
|         followthrough = total_time - waiting_time |  | ||||||
|     print("%17s: total=%7s wait=%7s ft=%7s size=%s (%s)" % |  | ||||||
|           ("%s-%s" % (event_type, result), |  | ||||||
|            abbrev(total_time), |  | ||||||
|            abbrev(waiting_time), |  | ||||||
|            abbrev(followthrough), |  | ||||||
|            naturalsize(total_bytes), |  | ||||||
|            time.ctime(started), |  | ||||||
|           )) |  | ||||||
| 
 |  | ||||||
| def show_usage(args): |  | ||||||
|     print("closed for renovation") |  | ||||||
|     return 0 |  | ||||||
|     if not os.path.exists("relay.sqlite"): |  | ||||||
|         raise click.UsageError( |  | ||||||
|             "cannot find relay.sqlite, please run from the server directory" |  | ||||||
|         ) |  | ||||||
|     oldest = None |  | ||||||
|     newest = None |  | ||||||
|     rendezvous_counters = defaultdict(int) |  | ||||||
|     transit_counters = defaultdict(int) |  | ||||||
|     total_transit_bytes = 0 |  | ||||||
|     db = get_db("relay.sqlite") |  | ||||||
|     c = db.execute("SELECT * FROM `usage`" |  | ||||||
|                    " ORDER BY `started` ASC LIMIT ?", |  | ||||||
|                    (args.n,)) |  | ||||||
|     for row in c.fetchall(): |  | ||||||
|         if row["type"] == "rendezvous": |  | ||||||
|             counters = rendezvous_counters |  | ||||||
|         elif row["type"] == "transit": |  | ||||||
|             counters = transit_counters |  | ||||||
|             total_transit_bytes += row["total_bytes"] |  | ||||||
|         else: |  | ||||||
|             continue |  | ||||||
|         counters["total"] += 1 |  | ||||||
|         counters[row["result"]] += 1 |  | ||||||
|         if oldest is None or row["started"] < oldest: |  | ||||||
|             oldest = row["started"] |  | ||||||
|         if newest is None or row["started"] > newest: |  | ||||||
|             newest = row["started"] |  | ||||||
|         event = (row["type"], row["started"], row["result"], |  | ||||||
|                  row["total_bytes"], row["waiting_time"], row["total_time"]) |  | ||||||
|         print_event(event) |  | ||||||
|     if rendezvous_counters["total"] or transit_counters["total"]: |  | ||||||
|         print("---") |  | ||||||
|         print("(most recent started %s ago)" % abbrev(time.time() - newest)) |  | ||||||
|     if rendezvous_counters["total"]: |  | ||||||
|         print("rendezvous events:") |  | ||||||
|         counters = rendezvous_counters |  | ||||||
|         elapsed = time.time() - oldest |  | ||||||
|         total = counters["total"] |  | ||||||
|         print(" %d events in %s (%.2f per hour)" % (total, abbrev(elapsed), |  | ||||||
|                                                     (3600 * total / elapsed))) |  | ||||||
|         print("", ", ".join(["%s=%d (%d%%)" % |  | ||||||
|                              (k, counters[k], (100.0 * counters[k] / total)) |  | ||||||
|                              for k in sorted(counters) |  | ||||||
|                              if k != "total"])) |  | ||||||
|     if transit_counters["total"]: |  | ||||||
|         print("transit events:") |  | ||||||
|         counters = transit_counters |  | ||||||
|         elapsed = time.time() - oldest |  | ||||||
|         total = counters["total"] |  | ||||||
|         print(" %d events in %s (%.2f per hour)" % (total, abbrev(elapsed), |  | ||||||
|                                                     (3600 * total / elapsed))) |  | ||||||
|         rate = total_transit_bytes / elapsed |  | ||||||
|         print(" %s total bytes, %sps" % (naturalsize(total_transit_bytes), |  | ||||||
|                                          naturalsize(rate))) |  | ||||||
|         print("", ", ".join(["%s=%d (%d%%)" % |  | ||||||
|                              (k, counters[k], (100.0 * counters[k] / total)) |  | ||||||
|                              for k in sorted(counters) |  | ||||||
|                              if k != "total"])) |  | ||||||
|     return 0 |  | ||||||
| 
 |  | ||||||
| def tail_usage(args): |  | ||||||
|     if not os.path.exists("relay.sqlite"): |  | ||||||
|         raise click.UsageError( |  | ||||||
|             "cannot find relay.sqlite, please run from the server directory" |  | ||||||
|         ) |  | ||||||
|     db = get_db("relay.sqlite") |  | ||||||
|     # we don't seem to have unique row IDs, so this is an inaccurate and |  | ||||||
|     # inefficient hack |  | ||||||
|     seen = set() |  | ||||||
|     try: |  | ||||||
|         while True: |  | ||||||
|             old = time.time() - 2*60*60 |  | ||||||
|             c = db.execute("SELECT * FROM `usage`" |  | ||||||
|                            " WHERE `started` > ?" |  | ||||||
|                            " ORDER BY `started` ASC", (old,)) |  | ||||||
|             for row in c.fetchall(): |  | ||||||
|                 event = (row["type"], row["started"], row["result"], |  | ||||||
|                          row["total_bytes"], row["waiting_time"], |  | ||||||
|                          row["total_time"]) |  | ||||||
|                 if event not in seen: |  | ||||||
|                     print_event(event) |  | ||||||
|                     seen.add(event) |  | ||||||
|             time.sleep(2) |  | ||||||
|     except KeyboardInterrupt: |  | ||||||
|         return 0 |  | ||||||
|     return 0 |  | ||||||
| 
 |  | ||||||
| def count_channels(args): |  | ||||||
|     if not os.path.exists("relay.sqlite"): |  | ||||||
|         raise click.UsageError( |  | ||||||
|             "cannot find relay.sqlite, please run from the server directory" |  | ||||||
|         ) |  | ||||||
|     db = get_db("relay.sqlite") |  | ||||||
|     c_list = [] |  | ||||||
|     c_dict = {} |  | ||||||
|     def add(key, value): |  | ||||||
|         c_list.append((key, value)) |  | ||||||
|         c_dict[key] = value |  | ||||||
|     OLD = time.time() - 10*60 |  | ||||||
|     def q(query, values=()): |  | ||||||
|         return list(db.execute(query, values).fetchone().values())[0] |  | ||||||
|     add("apps", q("SELECT COUNT(DISTINCT(`app_id`)) FROM `nameplates`")) |  | ||||||
| 
 |  | ||||||
|     add("total nameplates", q("SELECT COUNT() FROM `nameplates`")) |  | ||||||
|     add("waiting nameplates", q("SELECT COUNT() FROM `nameplates`" |  | ||||||
|                                 " WHERE `second` is null")) |  | ||||||
|     add("connected nameplates", q("SELECT COUNT() FROM `nameplates`" |  | ||||||
|                                   " WHERE `second` is not null")) |  | ||||||
|     add("stale nameplates", q("SELECT COUNT() FROM `nameplates`" |  | ||||||
|                               " where `updated` < ?", (OLD,))) |  | ||||||
| 
 |  | ||||||
|     add("total mailboxes", q("SELECT COUNT() FROM `mailboxes`")) |  | ||||||
|     add("waiting mailboxes", q("SELECT COUNT() FROM `mailboxes`" |  | ||||||
|                                 " WHERE `second` is null")) |  | ||||||
|     add("connected mailboxes", q("SELECT COUNT() FROM `mailboxes`" |  | ||||||
|                                  " WHERE `second` is not null")) |  | ||||||
| 
 |  | ||||||
|     stale_mailboxes = 0 |  | ||||||
|     for mbox_row in db.execute("SELECT * FROM `mailboxes`").fetchall(): |  | ||||||
|         newest = db.execute("SELECT `server_rx` FROM `messages`" |  | ||||||
|                             " WHERE `app_id`=? AND `mailbox_id`=?" |  | ||||||
|                             " ORDER BY `server_rx` DESC LIMIT 1", |  | ||||||
|                             (mbox_row["app_id"], mbox_row["id"])).fetchone() |  | ||||||
|         if newest and newest[0] < OLD: |  | ||||||
|             stale_mailboxes += 1 |  | ||||||
|     add("stale mailboxes", stale_mailboxes) |  | ||||||
| 
 |  | ||||||
|     add("messages", q("SELECT COUNT() FROM `messages`")) |  | ||||||
| 
 |  | ||||||
|     if args.json: |  | ||||||
|         print(json.dumps(c_dict)) |  | ||||||
|     else: |  | ||||||
|         for (key, value) in c_list: |  | ||||||
|             print(key, value) |  | ||||||
|     return 0 |  | ||||||
| 
 |  | ||||||
| def count_events(args): |  | ||||||
|     if not os.path.exists("relay.sqlite"): |  | ||||||
|         raise click.UsageError( |  | ||||||
|             "cannot find relay.sqlite, please run from the server directory" |  | ||||||
|         ) |  | ||||||
|     db = get_db("relay.sqlite") |  | ||||||
|     c_list = [] |  | ||||||
|     c_dict = {} |  | ||||||
|     def add(key, value): |  | ||||||
|         c_list.append((key, value)) |  | ||||||
|         c_dict[key] = value |  | ||||||
|     def q(query, values=()): |  | ||||||
|         return list(db.execute(query, values).fetchone().values())[0] |  | ||||||
| 
 |  | ||||||
|     add("apps", q("SELECT COUNT(DISTINCT(`app_id`)) FROM `nameplate_usage`")) |  | ||||||
| 
 |  | ||||||
|     add("total nameplates", q("SELECT COUNT() FROM `nameplate_usage`")) |  | ||||||
|     add("happy nameplates", q("SELECT COUNT() FROM `nameplate_usage`" |  | ||||||
|                               " WHERE `result`='happy'")) |  | ||||||
|     add("lonely nameplates", q("SELECT COUNT() FROM `nameplate_usage`" |  | ||||||
|                                " WHERE `result`='lonely'")) |  | ||||||
|     add("pruney nameplates", q("SELECT COUNT() FROM `nameplate_usage`" |  | ||||||
|                                " WHERE `result`='pruney'")) |  | ||||||
|     add("crowded nameplates", q("SELECT COUNT() FROM `nameplate_usage`" |  | ||||||
|                                 " WHERE `result`='crowded'")) |  | ||||||
| 
 |  | ||||||
|     add("total mailboxes", q("SELECT COUNT() FROM `mailbox_usage`")) |  | ||||||
|     add("happy mailboxes", q("SELECT COUNT() FROM `mailbox_usage`" |  | ||||||
|                              " WHERE `result`='happy'")) |  | ||||||
|     add("scary mailboxes", q("SELECT COUNT() FROM `mailbox_usage`" |  | ||||||
|                              " WHERE `result`='scary'")) |  | ||||||
|     add("lonely mailboxes", q("SELECT COUNT() FROM `mailbox_usage`" |  | ||||||
|                               " WHERE `result`='lonely'")) |  | ||||||
|     add("errory mailboxes", q("SELECT COUNT() FROM `mailbox_usage`" |  | ||||||
|                               " WHERE `result`='errory'")) |  | ||||||
|     add("pruney mailboxes", q("SELECT COUNT() FROM `mailbox_usage`" |  | ||||||
|                               " WHERE `result`='pruney'")) |  | ||||||
|     add("crowded mailboxes", q("SELECT COUNT() FROM `mailbox_usage`" |  | ||||||
|                                " WHERE `result`='crowded'")) |  | ||||||
| 
 |  | ||||||
|     add("total transit", q("SELECT COUNT() FROM `transit_usage`")) |  | ||||||
|     add("happy transit", q("SELECT COUNT() FROM `transit_usage`" |  | ||||||
|                            " WHERE `result`='happy'")) |  | ||||||
|     add("lonely transit", q("SELECT COUNT() FROM `transit_usage`" |  | ||||||
|                             " WHERE `result`='lonely'")) |  | ||||||
|     add("errory transit", q("SELECT COUNT() FROM `transit_usage`" |  | ||||||
|                             " WHERE `result`='errory'")) |  | ||||||
| 
 |  | ||||||
|     add("transit bytes", q("SELECT SUM(`total_bytes`) FROM `transit_usage`")) |  | ||||||
| 
 |  | ||||||
|     if args.json: |  | ||||||
|         print(json.dumps(c_dict)) |  | ||||||
|     else: |  | ||||||
|         for (key, value) in c_list: |  | ||||||
|             print(key, value) |  | ||||||
|     return 0 |  | ||||||
|  | @ -1,126 +0,0 @@ | ||||||
| from __future__ import unicode_literals |  | ||||||
| import os |  | ||||||
| import sqlite3 |  | ||||||
| import tempfile |  | ||||||
| from pkg_resources import resource_string |  | ||||||
| from twisted.python import log |  | ||||||
| 
 |  | ||||||
| class DBError(Exception): |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| def get_schema(version): |  | ||||||
|     schema_bytes = resource_string("wormhole.server", |  | ||||||
|                                    "db-schemas/v%d.sql" % version) |  | ||||||
|     return schema_bytes.decode("utf-8") |  | ||||||
| 
 |  | ||||||
| def get_upgrader(new_version): |  | ||||||
|     schema_bytes = resource_string("wormhole.server", |  | ||||||
|                                    "db-schemas/upgrade-to-v%d.sql" % new_version) |  | ||||||
|     return schema_bytes.decode("utf-8") |  | ||||||
| 
 |  | ||||||
| TARGET_VERSION = 3 |  | ||||||
| 
 |  | ||||||
| def dict_factory(cursor, row): |  | ||||||
|     d = {} |  | ||||||
|     for idx, col in enumerate(cursor.description): |  | ||||||
|         d[col[0]] = row[idx] |  | ||||||
|     return d |  | ||||||
| 
 |  | ||||||
| def _initialize_db_schema(db, target_version): |  | ||||||
|     """Creates the application schema in the given database. |  | ||||||
|     """ |  | ||||||
|     log.msg("populating new database with schema v%s" % target_version) |  | ||||||
|     schema = get_schema(target_version) |  | ||||||
|     db.executescript(schema) |  | ||||||
|     db.execute("INSERT INTO version (version) VALUES (?)", |  | ||||||
|                (target_version,)) |  | ||||||
|     db.commit() |  | ||||||
| 
 |  | ||||||
| def _initialize_db_connection(db): |  | ||||||
|     """Sets up the db connection object with a row factory and with necessary |  | ||||||
|     foreign key settings. |  | ||||||
|     """ |  | ||||||
|     db.row_factory = dict_factory |  | ||||||
|     db.execute("PRAGMA foreign_keys = ON") |  | ||||||
|     problems = db.execute("PRAGMA foreign_key_check").fetchall() |  | ||||||
|     if problems: |  | ||||||
|         raise DBError("failed foreign key check: %s" % (problems,)) |  | ||||||
| 
 |  | ||||||
| def _open_db_connection(dbfile): |  | ||||||
|     """Open a new connection to the SQLite3 database at the given path. |  | ||||||
|     """ |  | ||||||
|     try: |  | ||||||
|         db = sqlite3.connect(dbfile) |  | ||||||
|     except (EnvironmentError, sqlite3.OperationalError) as e: |  | ||||||
|         raise DBError("Unable to create/open db file %s: %s" % (dbfile, e)) |  | ||||||
|     _initialize_db_connection(db) |  | ||||||
|     return db |  | ||||||
| 
 |  | ||||||
| def _get_temporary_dbfile(dbfile): |  | ||||||
|     """Get a temporary filename near the given path. |  | ||||||
|     """ |  | ||||||
|     fd, name = tempfile.mkstemp( |  | ||||||
|         prefix=os.path.basename(dbfile) + ".", |  | ||||||
|         dir=os.path.dirname(dbfile) |  | ||||||
|     ) |  | ||||||
|     os.close(fd) |  | ||||||
|     return name |  | ||||||
| 
 |  | ||||||
| def _atomic_create_and_initialize_db(dbfile, target_version): |  | ||||||
|     """Create and return a new database, initialized with the application |  | ||||||
|     schema. |  | ||||||
| 
 |  | ||||||
|     If anything goes wrong, nothing is left at the ``dbfile`` path. |  | ||||||
|     """ |  | ||||||
|     temp_dbfile = _get_temporary_dbfile(dbfile) |  | ||||||
|     db = _open_db_connection(temp_dbfile) |  | ||||||
|     _initialize_db_schema(db, target_version) |  | ||||||
|     db.close() |  | ||||||
|     os.rename(temp_dbfile, dbfile) |  | ||||||
|     return _open_db_connection(dbfile) |  | ||||||
| 
 |  | ||||||
| def get_db(dbfile, target_version=TARGET_VERSION): |  | ||||||
|     """Open or create the given db file. The parent directory must exist. |  | ||||||
|     Returns the db connection object, or raises DBError. |  | ||||||
|     """ |  | ||||||
|     if dbfile == ":memory:": |  | ||||||
|         db = _open_db_connection(dbfile) |  | ||||||
|         _initialize_db_schema(db, target_version) |  | ||||||
|     elif os.path.exists(dbfile): |  | ||||||
|         db = _open_db_connection(dbfile) |  | ||||||
|     else: |  | ||||||
|         db = _atomic_create_and_initialize_db(dbfile, target_version) |  | ||||||
| 
 |  | ||||||
|     try: |  | ||||||
|         version = db.execute("SELECT version FROM version").fetchone()["version"] |  | ||||||
|     except sqlite3.DatabaseError as e: |  | ||||||
|         # this indicates that the file is not a compatible database format. |  | ||||||
|         # Perhaps it was created with an old version, or it might be junk. |  | ||||||
|         raise DBError("db file is unusable: %s" % e) |  | ||||||
| 
 |  | ||||||
|     while version < target_version: |  | ||||||
|         log.msg(" need to upgrade from %s to %s" % (version, target_version)) |  | ||||||
|         try: |  | ||||||
|             upgrader = get_upgrader(version+1) |  | ||||||
|         except ValueError: # ResourceError?? |  | ||||||
|             log.msg(" unable to upgrade %s to %s" % (version, version+1)) |  | ||||||
|             raise DBError("Unable to upgrade %s to version %s, left at %s" |  | ||||||
|                           % (dbfile, version+1, version)) |  | ||||||
|         log.msg(" executing upgrader v%s->v%s" % (version, version+1)) |  | ||||||
|         db.executescript(upgrader) |  | ||||||
|         db.commit() |  | ||||||
|         version = version+1 |  | ||||||
| 
 |  | ||||||
|     if version != target_version: |  | ||||||
|         raise DBError("Unable to handle db version %s" % version) |  | ||||||
| 
 |  | ||||||
|     return db |  | ||||||
| 
 |  | ||||||
| def dump_db(db): |  | ||||||
|     # to let _iterdump work, we need to restore the original row factory |  | ||||||
|     orig = db.row_factory |  | ||||||
|     try: |  | ||||||
|         db.row_factory = sqlite3.Row |  | ||||||
|         return "".join(db.iterdump()) |  | ||||||
|     finally: |  | ||||||
|         db.row_factory = orig |  | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| DROP TABLE `nameplates`; |  | ||||||
| DROP TABLE `messages`; |  | ||||||
| DROP TABLE `mailboxes`; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- Wormhole codes use a "nameplate": a short name which is only used to |  | ||||||
| -- reference a specific (long-named) mailbox. The codes only use numeric |  | ||||||
| -- nameplates, but the protocol and server allow can use arbitrary strings. |  | ||||||
| CREATE TABLE `nameplates` |  | ||||||
| ( |  | ||||||
|  `id` INTEGER PRIMARY KEY AUTOINCREMENT, |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `name` VARCHAR, |  | ||||||
|  `mailbox_id` VARCHAR REFERENCES `mailboxes`(`id`), |  | ||||||
|  `request_id` VARCHAR -- from 'allocate' message, for future deduplication |  | ||||||
| ); |  | ||||||
| CREATE INDEX `nameplates_idx` ON `nameplates` (`app_id`, `name`); |  | ||||||
| CREATE INDEX `nameplates_mailbox_idx` ON `nameplates` (`app_id`, `mailbox_id`); |  | ||||||
| CREATE INDEX `nameplates_request_idx` ON `nameplates` (`app_id`, `request_id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `nameplate_sides` |  | ||||||
| ( |  | ||||||
|  `nameplates_id` REFERENCES `nameplates`(`id`), |  | ||||||
|  `claimed` BOOLEAN, -- True after claim(), False after release() |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `added` INTEGER -- time when this side first claimed the nameplate |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- Clients exchange messages through a "mailbox", which has a long (randomly |  | ||||||
| -- unique) identifier and a queue of messages. |  | ||||||
| -- `id` is randomly-generated and unique across all apps. |  | ||||||
| CREATE TABLE `mailboxes` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `id` VARCHAR PRIMARY KEY, |  | ||||||
|  `updated` INTEGER, -- time of last activity, used for pruning |  | ||||||
|  `for_nameplate` BOOLEAN -- allocated for a nameplate, not standalone |  | ||||||
| ); |  | ||||||
| CREATE INDEX `mailboxes_idx` ON `mailboxes` (`app_id`, `id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `mailbox_sides` |  | ||||||
| ( |  | ||||||
|  `mailbox_id` REFERENCES `mailboxes`(`id`), |  | ||||||
|  `opened` BOOLEAN, -- True after open(), False after close() |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `added` INTEGER, -- time when this side first opened the mailbox |  | ||||||
|  `mood` VARCHAR |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `messages` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `mailbox_id` VARCHAR, |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `phase` VARCHAR, -- numeric or string |  | ||||||
|  `body` VARCHAR, |  | ||||||
|  `server_rx` INTEGER, |  | ||||||
|  `msg_id` VARCHAR |  | ||||||
| ); |  | ||||||
| CREATE INDEX `messages_idx` ON `messages` (`app_id`, `mailbox_id`); |  | ||||||
| 
 |  | ||||||
| ALTER TABLE `mailbox_usage` ADD COLUMN `for_nameplate` BOOLEAN; |  | ||||||
| CREATE INDEX `mailbox_usage_result_idx` ON `mailbox_usage` (`result`); |  | ||||||
| CREATE INDEX `transit_usage_result_idx` ON `transit_usage` (`result`); |  | ||||||
| 
 |  | ||||||
| DELETE FROM `version`; |  | ||||||
| INSERT INTO `version` (`version`) VALUES (3); |  | ||||||
|  | @ -1,105 +0,0 @@ | ||||||
| 
 |  | ||||||
| -- note: anything which isn't an boolean, integer, or human-readable unicode |  | ||||||
| -- string, (i.e. binary strings) will be stored as hex |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `version` |  | ||||||
| ( |  | ||||||
|  `version` INTEGER -- contains one row, set to 2 |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- Wormhole codes use a "nameplate": a short identifier which is only used to |  | ||||||
| -- reference a specific (long-named) mailbox. The codes only use numeric |  | ||||||
| -- nameplates, but the protocol and server allow can use arbitrary strings. |  | ||||||
| CREATE TABLE `nameplates` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `id` VARCHAR, |  | ||||||
|  `mailbox_id` VARCHAR, -- really a foreign key |  | ||||||
|  `side1` VARCHAR, -- side name, or NULL |  | ||||||
|  `side2` VARCHAR, -- side name, or NULL |  | ||||||
|  `request_id` VARCHAR, -- from 'allocate' message, for future deduplication |  | ||||||
|  `crowded` BOOLEAN, -- at some point, three or more sides were involved |  | ||||||
|  `updated` INTEGER, -- time of last activity, used for pruning |  | ||||||
|  -- timing data |  | ||||||
|  `started` INTEGER, -- time when nameplace was opened |  | ||||||
|  `second` INTEGER -- time when second side opened |  | ||||||
| ); |  | ||||||
| CREATE INDEX `nameplates_idx` ON `nameplates` (`app_id`, `id`); |  | ||||||
| CREATE INDEX `nameplates_updated_idx` ON `nameplates` (`app_id`, `updated`); |  | ||||||
| CREATE INDEX `nameplates_mailbox_idx` ON `nameplates` (`app_id`, `mailbox_id`); |  | ||||||
| CREATE INDEX `nameplates_request_idx` ON `nameplates` (`app_id`, `request_id`); |  | ||||||
| 
 |  | ||||||
| -- Clients exchange messages through a "mailbox", which has a long (randomly |  | ||||||
| -- unique) identifier and a queue of messages. |  | ||||||
| CREATE TABLE `mailboxes` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `id` VARCHAR, |  | ||||||
|  `side1` VARCHAR, -- side name, or NULL |  | ||||||
|  `side2` VARCHAR, -- side name, or NULL |  | ||||||
|  `crowded` BOOLEAN, -- at some point, three or more sides were involved |  | ||||||
|  `first_mood` VARCHAR, |  | ||||||
|  -- timing data for the mailbox itself |  | ||||||
|  `started` INTEGER, -- time when opened |  | ||||||
|  `second` INTEGER -- time when second side opened |  | ||||||
| ); |  | ||||||
| CREATE INDEX `mailboxes_idx` ON `mailboxes` (`app_id`, `id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `messages` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `mailbox_id` VARCHAR, |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `phase` VARCHAR, -- numeric or string |  | ||||||
|  `body` VARCHAR, |  | ||||||
|  `server_rx` INTEGER, |  | ||||||
|  `msg_id` VARCHAR |  | ||||||
| ); |  | ||||||
| CREATE INDEX `messages_idx` ON `messages` (`app_id`, `mailbox_id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `nameplate_usage` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `started` INTEGER, -- seconds since epoch, rounded to "blur time" |  | ||||||
|  `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None |  | ||||||
|  `total_time` INTEGER, -- seconds from open to last close/prune |  | ||||||
|  `result` VARCHAR -- happy, lonely, pruney, crowded |  | ||||||
|  -- nameplate moods: |  | ||||||
|  --  "happy": two sides open and close |  | ||||||
|  --  "lonely": one side opens and closes (no response from 2nd side) |  | ||||||
|  --  "pruney": channels which get pruned for inactivity |  | ||||||
|  --  "crowded": three or more sides were involved |  | ||||||
| ); |  | ||||||
| CREATE INDEX `nameplate_usage_idx` ON `nameplate_usage` (`app_id`, `started`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `mailbox_usage` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `started` INTEGER, -- seconds since epoch, rounded to "blur time" |  | ||||||
|  `total_time` INTEGER, -- seconds from open to last close |  | ||||||
|  `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None |  | ||||||
|  `result` VARCHAR -- happy, scary, lonely, errory, pruney |  | ||||||
|  -- rendezvous moods: |  | ||||||
|  --  "happy": both sides close with mood=happy |  | ||||||
|  --  "scary": any side closes with mood=scary (bad MAC, probably wrong pw) |  | ||||||
|  --  "lonely": any side closes with mood=lonely (no response from 2nd side) |  | ||||||
|  --  "errory": any side closes with mood=errory (other errors) |  | ||||||
|  --  "pruney": channels which get pruned for inactivity |  | ||||||
|  --  "crowded": three or more sides were involved |  | ||||||
| ); |  | ||||||
| CREATE INDEX `mailbox_usage_idx` ON `mailbox_usage` (`app_id`, `started`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `transit_usage` |  | ||||||
| ( |  | ||||||
|  `started` INTEGER, -- seconds since epoch, rounded to "blur time" |  | ||||||
|  `total_time` INTEGER, -- seconds from open to last close |  | ||||||
|  `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None |  | ||||||
|  `total_bytes` INTEGER, -- total bytes relayed (both directions) |  | ||||||
|  `result` VARCHAR -- happy, scary, lonely, errory, pruney |  | ||||||
|  -- transit moods: |  | ||||||
|  --  "errory": one side gave the wrong handshake |  | ||||||
|  --  "lonely": good handshake, but the other side never showed up |  | ||||||
|  --  "happy": both sides gave correct handshake |  | ||||||
| ); |  | ||||||
| CREATE INDEX `transit_usage_idx` ON `transit_usage` (`started`); |  | ||||||
|  | @ -1,115 +0,0 @@ | ||||||
| 
 |  | ||||||
| -- note: anything which isn't an boolean, integer, or human-readable unicode |  | ||||||
| -- string, (i.e. binary strings) will be stored as hex |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `version` |  | ||||||
| ( |  | ||||||
|  `version` INTEGER -- contains one row, set to 3 |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- Wormhole codes use a "nameplate": a short name which is only used to |  | ||||||
| -- reference a specific (long-named) mailbox. The codes only use numeric |  | ||||||
| -- nameplates, but the protocol and server allow can use arbitrary strings. |  | ||||||
| CREATE TABLE `nameplates` |  | ||||||
| ( |  | ||||||
|  `id` INTEGER PRIMARY KEY AUTOINCREMENT, |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `name` VARCHAR, |  | ||||||
|  `mailbox_id` VARCHAR REFERENCES `mailboxes`(`id`), |  | ||||||
|  `request_id` VARCHAR -- from 'allocate' message, for future deduplication |  | ||||||
| ); |  | ||||||
| CREATE INDEX `nameplates_idx` ON `nameplates` (`app_id`, `name`); |  | ||||||
| CREATE INDEX `nameplates_mailbox_idx` ON `nameplates` (`app_id`, `mailbox_id`); |  | ||||||
| CREATE INDEX `nameplates_request_idx` ON `nameplates` (`app_id`, `request_id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `nameplate_sides` |  | ||||||
| ( |  | ||||||
|  `nameplates_id` REFERENCES `nameplates`(`id`), |  | ||||||
|  `claimed` BOOLEAN, -- True after claim(), False after release() |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `added` INTEGER -- time when this side first claimed the nameplate |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- Clients exchange messages through a "mailbox", which has a long (randomly |  | ||||||
| -- unique) identifier and a queue of messages. |  | ||||||
| -- `id` is randomly-generated and unique across all apps. |  | ||||||
| CREATE TABLE `mailboxes` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `id` VARCHAR PRIMARY KEY, |  | ||||||
|  `updated` INTEGER, -- time of last activity, used for pruning |  | ||||||
|  `for_nameplate` BOOLEAN -- allocated for a nameplate, not standalone |  | ||||||
| ); |  | ||||||
| CREATE INDEX `mailboxes_idx` ON `mailboxes` (`app_id`, `id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `mailbox_sides` |  | ||||||
| ( |  | ||||||
|  `mailbox_id` REFERENCES `mailboxes`(`id`), |  | ||||||
|  `opened` BOOLEAN, -- True after open(), False after close() |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `added` INTEGER, -- time when this side first opened the mailbox |  | ||||||
|  `mood` VARCHAR |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `messages` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `mailbox_id` VARCHAR, |  | ||||||
|  `side` VARCHAR, |  | ||||||
|  `phase` VARCHAR, -- numeric or string |  | ||||||
|  `body` VARCHAR, |  | ||||||
|  `server_rx` INTEGER, |  | ||||||
|  `msg_id` VARCHAR |  | ||||||
| ); |  | ||||||
| CREATE INDEX `messages_idx` ON `messages` (`app_id`, `mailbox_id`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `nameplate_usage` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `started` INTEGER, -- seconds since epoch, rounded to "blur time" |  | ||||||
|  `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None |  | ||||||
|  `total_time` INTEGER, -- seconds from open to last close/prune |  | ||||||
|  `result` VARCHAR -- happy, lonely, pruney, crowded |  | ||||||
|  -- nameplate moods: |  | ||||||
|  --  "happy": two sides open and close |  | ||||||
|  --  "lonely": one side opens and closes (no response from 2nd side) |  | ||||||
|  --  "pruney": channels which get pruned for inactivity |  | ||||||
|  --  "crowded": three or more sides were involved |  | ||||||
| ); |  | ||||||
| CREATE INDEX `nameplate_usage_idx` ON `nameplate_usage` (`app_id`, `started`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `mailbox_usage` |  | ||||||
| ( |  | ||||||
|  `app_id` VARCHAR, |  | ||||||
|  `for_nameplate` BOOLEAN, -- allocated for a nameplate, not standalone |  | ||||||
|  `started` INTEGER, -- seconds since epoch, rounded to "blur time" |  | ||||||
|  `total_time` INTEGER, -- seconds from open to last close |  | ||||||
|  `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None |  | ||||||
|  `result` VARCHAR -- happy, scary, lonely, errory, pruney |  | ||||||
|  -- rendezvous moods: |  | ||||||
|  --  "happy": both sides close with mood=happy |  | ||||||
|  --  "scary": any side closes with mood=scary (bad MAC, probably wrong pw) |  | ||||||
|  --  "lonely": any side closes with mood=lonely (no response from 2nd side) |  | ||||||
|  --  "errory": any side closes with mood=errory (other errors) |  | ||||||
|  --  "pruney": channels which get pruned for inactivity |  | ||||||
|  --  "crowded": three or more sides were involved |  | ||||||
| ); |  | ||||||
| CREATE INDEX `mailbox_usage_idx` ON `mailbox_usage` (`app_id`, `started`); |  | ||||||
| CREATE INDEX `mailbox_usage_result_idx` ON `mailbox_usage` (`result`); |  | ||||||
| 
 |  | ||||||
| CREATE TABLE `transit_usage` |  | ||||||
| ( |  | ||||||
|  `started` INTEGER, -- seconds since epoch, rounded to "blur time" |  | ||||||
|  `total_time` INTEGER, -- seconds from open to last close |  | ||||||
|  `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None |  | ||||||
|  `total_bytes` INTEGER, -- total bytes relayed (both directions) |  | ||||||
|  `result` VARCHAR -- happy, scary, lonely, errory, pruney |  | ||||||
|  -- transit moods: |  | ||||||
|  --  "errory": one side gave the wrong handshake |  | ||||||
|  --  "lonely": good handshake, but the other side never showed up |  | ||||||
|  --  "happy": both sides gave correct handshake |  | ||||||
| ); |  | ||||||
| CREATE INDEX `transit_usage_idx` ON `transit_usage` (`started`); |  | ||||||
| CREATE INDEX `transit_usage_result_idx` ON `transit_usage` (`result`); |  | ||||||
|  | @ -1,181 +0,0 @@ | ||||||
| # NO unicode_literals or static.Data() will break, because it demands |  | ||||||
| # a str on Python 2 |  | ||||||
| from __future__ import print_function |  | ||||||
| import os, time, json |  | ||||||
| try: |  | ||||||
|     # 'resource' is unix-only |  | ||||||
|     from resource import getrlimit, setrlimit, RLIMIT_NOFILE |  | ||||||
| except ImportError: # pragma: nocover |  | ||||||
|     getrlimit, setrlimit, RLIMIT_NOFILE = None, None, None # pragma: nocover |  | ||||||
| from twisted.python import log |  | ||||||
| from twisted.internet import reactor, endpoints |  | ||||||
| from twisted.application import service, internet |  | ||||||
| from twisted.web import server, static |  | ||||||
| from twisted.web.resource import Resource |  | ||||||
| from autobahn.twisted.resource import WebSocketResource |  | ||||||
| from .database import get_db |  | ||||||
| from .rendezvous import Rendezvous |  | ||||||
| from .rendezvous_websocket import WebSocketRendezvousFactory |  | ||||||
| from .transit_server import Transit |  | ||||||
| 
 |  | ||||||
| SECONDS = 1.0 |  | ||||||
| MINUTE = 60*SECONDS |  | ||||||
| 
 |  | ||||||
| CHANNEL_EXPIRATION_TIME = 11*MINUTE |  | ||||||
| EXPIRATION_CHECK_PERIOD = 10*MINUTE |  | ||||||
| 
 |  | ||||||
| class Root(Resource): |  | ||||||
|     # child_FOO is a nevow thing, not a twisted.web.resource thing |  | ||||||
|     def __init__(self): |  | ||||||
|         Resource.__init__(self) |  | ||||||
|         self.putChild(b"", static.Data(b"Wormhole Relay\n", "text/plain")) |  | ||||||
| 
 |  | ||||||
| class PrivacyEnhancedSite(server.Site): |  | ||||||
|     logRequests = True |  | ||||||
|     def log(self, request): |  | ||||||
|         if self.logRequests: |  | ||||||
|             return server.Site.log(self, request) |  | ||||||
| 
 |  | ||||||
| class RelayServer(service.MultiService): |  | ||||||
| 
 |  | ||||||
|     def __init__(self, rendezvous_web_port, transit_port, |  | ||||||
|                  advertise_version, db_url=":memory:", blur_usage=None, |  | ||||||
|                  signal_error=None, stats_file=None, allow_list=True, |  | ||||||
|                  websocket_protocol_options=()): |  | ||||||
|         service.MultiService.__init__(self) |  | ||||||
|         self._blur_usage = blur_usage |  | ||||||
|         self._allow_list = allow_list |  | ||||||
|         self._db_url = db_url |  | ||||||
| 
 |  | ||||||
|         db = get_db(db_url) |  | ||||||
|         welcome = { |  | ||||||
|             # adding .motd will cause all clients to display the message, |  | ||||||
|             # then keep running normally |  | ||||||
|             #"motd": "Welcome to the public relay.\nPlease enjoy this service.", |  | ||||||
| 
 |  | ||||||
|             # adding .error will cause all clients to fail, with this message |  | ||||||
|             #"error": "This server has been disabled, see URL for details.", |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         if advertise_version: |  | ||||||
|             # The primary (python CLI) implementation will emit a message if |  | ||||||
|             # its version does not match this key. If/when we have |  | ||||||
|             # distributions which include older version, but we still expect |  | ||||||
|             # them to be compatible, stop sending this key. |  | ||||||
|             welcome["current_cli_version"] = advertise_version |  | ||||||
|         if signal_error: |  | ||||||
|             welcome["error"] = signal_error |  | ||||||
| 
 |  | ||||||
|         self._rendezvous = Rendezvous(db, welcome, blur_usage, self._allow_list) |  | ||||||
|         self._rendezvous.setServiceParent(self) # for the pruning timer |  | ||||||
| 
 |  | ||||||
|         root = Root() |  | ||||||
|         wsrf = WebSocketRendezvousFactory(None, self._rendezvous) |  | ||||||
|         _set_options(websocket_protocol_options, wsrf) |  | ||||||
|         root.putChild(b"v1", WebSocketResource(wsrf)) |  | ||||||
| 
 |  | ||||||
|         site = PrivacyEnhancedSite(root) |  | ||||||
|         if blur_usage: |  | ||||||
|             site.logRequests = False |  | ||||||
| 
 |  | ||||||
|         r = endpoints.serverFromString(reactor, rendezvous_web_port) |  | ||||||
|         rendezvous_web_service = internet.StreamServerEndpointService(r, site) |  | ||||||
|         rendezvous_web_service.setServiceParent(self) |  | ||||||
| 
 |  | ||||||
|         if transit_port: |  | ||||||
|             transit = Transit(db, blur_usage) |  | ||||||
|             transit.setServiceParent(self) # for the timer |  | ||||||
|             t = endpoints.serverFromString(reactor, transit_port) |  | ||||||
|             transit_service = internet.StreamServerEndpointService(t, transit) |  | ||||||
|             transit_service.setServiceParent(self) |  | ||||||
| 
 |  | ||||||
|         self._stats_file = stats_file |  | ||||||
|         if self._stats_file and os.path.exists(self._stats_file): |  | ||||||
|             os.unlink(self._stats_file) |  | ||||||
|             # this will be regenerated immediately, but if something goes |  | ||||||
|             # wrong in dump_stats(), it's better to have a missing file than |  | ||||||
|             # a stale one |  | ||||||
|         t = internet.TimerService(EXPIRATION_CHECK_PERIOD, self.timer) |  | ||||||
|         t.setServiceParent(self) |  | ||||||
| 
 |  | ||||||
|         # make some things accessible for tests |  | ||||||
|         self._db = db |  | ||||||
|         self._root = root |  | ||||||
|         self._rendezvous_web_service = rendezvous_web_service |  | ||||||
|         self._rendezvous_websocket = wsrf |  | ||||||
|         self._transit = None |  | ||||||
|         if transit_port: |  | ||||||
|             self._transit = transit |  | ||||||
|             self._transit_service = transit_service |  | ||||||
| 
 |  | ||||||
|     def increase_rlimits(self): |  | ||||||
|         if getrlimit is None: |  | ||||||
|             log.msg("unable to import 'resource', leaving rlimit alone") |  | ||||||
|             return |  | ||||||
|         soft, hard = getrlimit(RLIMIT_NOFILE) |  | ||||||
|         if soft >= 10000: |  | ||||||
|             log.msg("RLIMIT_NOFILE.soft was %d, leaving it alone" % soft) |  | ||||||
|             return |  | ||||||
|         # OS-X defaults to soft=7168, and reports a huge number for 'hard', |  | ||||||
|         # but won't accept anything more than soft=10240, so we can't just |  | ||||||
|         # set soft=hard. Linux returns (1024, 1048576) and is fine with |  | ||||||
|         # soft=hard. Cygwin is reported to return (256,-1) and accepts up to |  | ||||||
|         # soft=3200. So we try multiple values until something works. |  | ||||||
|         for newlimit in [hard, 10000, 3200, 1024]: |  | ||||||
|             log.msg("changing RLIMIT_NOFILE from (%s,%s) to (%s,%s)" % |  | ||||||
|                     (soft, hard, newlimit, hard)) |  | ||||||
|             try: |  | ||||||
|                 setrlimit(RLIMIT_NOFILE, (newlimit, hard)) |  | ||||||
|                 log.msg("setrlimit successful") |  | ||||||
|                 return |  | ||||||
|             except ValueError as e: |  | ||||||
|                 log.msg("error during setrlimit: %s" % e) |  | ||||||
|                 continue |  | ||||||
|             except: |  | ||||||
|                 log.msg("other error during setrlimit, leaving it alone") |  | ||||||
|                 log.err() |  | ||||||
|                 return |  | ||||||
|         log.msg("unable to change rlimit, leaving it alone") |  | ||||||
| 
 |  | ||||||
|     def startService(self): |  | ||||||
|         service.MultiService.startService(self) |  | ||||||
|         self.increase_rlimits() |  | ||||||
|         log.msg("websocket listening on /wormhole-relay/ws") |  | ||||||
|         log.msg("Wormhole relay server (Rendezvous and Transit) running") |  | ||||||
|         if self._blur_usage: |  | ||||||
|             log.msg("blurring access times to %d seconds" % self._blur_usage) |  | ||||||
|             log.msg("not logging HTTP requests or Transit connections") |  | ||||||
|         else: |  | ||||||
|             log.msg("not blurring access times") |  | ||||||
|         if not self._allow_list: |  | ||||||
|             log.msg("listing of allocated nameplates disallowed") |  | ||||||
| 
 |  | ||||||
|     def timer(self): |  | ||||||
|         now = time.time() |  | ||||||
|         old = now - CHANNEL_EXPIRATION_TIME |  | ||||||
|         self._rendezvous.prune_all_apps(now, old) |  | ||||||
|         self.dump_stats(now, validity=EXPIRATION_CHECK_PERIOD+60) |  | ||||||
| 
 |  | ||||||
|     def dump_stats(self, now, validity): |  | ||||||
|         if not self._stats_file: |  | ||||||
|             return |  | ||||||
|         tmpfn = self._stats_file + ".tmp" |  | ||||||
| 
 |  | ||||||
|         data = {} |  | ||||||
|         data["created"] = now |  | ||||||
|         data["valid_until"] = now + validity |  | ||||||
| 
 |  | ||||||
|         start = time.time() |  | ||||||
|         data["rendezvous"] = self._rendezvous.get_stats() |  | ||||||
|         data["transit"] = self._transit.get_stats() |  | ||||||
|         log.msg("get_stats took:", time.time() - start) |  | ||||||
| 
 |  | ||||||
|         with open(tmpfn, "wb") as f: |  | ||||||
|             # json.dump(f) has str-vs-unicode issues on py2-vs-py3 |  | ||||||
|             f.write(json.dumps(data, indent=1).encode("utf-8")) |  | ||||||
|             f.write(b"\n") |  | ||||||
|         os.rename(tmpfn, self._stats_file) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def _set_options(options, factory): |  | ||||||
|     factory.setProtocolOptions(**dict(options)) |  | ||||||
|  | @ -1,61 +0,0 @@ | ||||||
| from __future__ import print_function, unicode_literals |  | ||||||
| import os |  | ||||||
| from twisted.python import filepath |  | ||||||
| from twisted.trial import unittest |  | ||||||
| from ..server import database |  | ||||||
| from ..server.database import get_db, TARGET_VERSION, dump_db |  | ||||||
| 
 |  | ||||||
| class DB(unittest.TestCase): |  | ||||||
|     def test_create_default(self): |  | ||||||
|         db_url = ":memory:" |  | ||||||
|         db = get_db(db_url) |  | ||||||
|         rows = db.execute("SELECT * FROM version").fetchall() |  | ||||||
|         self.assertEqual(len(rows), 1) |  | ||||||
|         self.assertEqual(rows[0]["version"], TARGET_VERSION) |  | ||||||
| 
 |  | ||||||
|     def test_failed_create_allows_subsequent_create(self): |  | ||||||
|         patch = self.patch(database, "get_schema", lambda version: b"this is a broken schema") |  | ||||||
|         dbfile = filepath.FilePath(self.mktemp()) |  | ||||||
|         self.assertRaises(Exception, lambda: get_db(dbfile.path)) |  | ||||||
|         patch.restore() |  | ||||||
|         get_db(dbfile.path) |  | ||||||
| 
 |  | ||||||
|     def test_upgrade(self): |  | ||||||
|         basedir = self.mktemp() |  | ||||||
|         os.mkdir(basedir) |  | ||||||
|         fn = os.path.join(basedir, "upgrade.db") |  | ||||||
|         self.assertNotEqual(TARGET_VERSION, 2) |  | ||||||
| 
 |  | ||||||
|         # create an old-version DB in a file |  | ||||||
|         db = get_db(fn, 2) |  | ||||||
|         rows = db.execute("SELECT * FROM version").fetchall() |  | ||||||
|         self.assertEqual(len(rows), 1) |  | ||||||
|         self.assertEqual(rows[0]["version"], 2) |  | ||||||
|         del db |  | ||||||
| 
 |  | ||||||
|         # then upgrade the file to the latest version |  | ||||||
|         dbA = get_db(fn, TARGET_VERSION) |  | ||||||
|         rows = dbA.execute("SELECT * FROM version").fetchall() |  | ||||||
|         self.assertEqual(len(rows), 1) |  | ||||||
|         self.assertEqual(rows[0]["version"], TARGET_VERSION) |  | ||||||
|         dbA_text = dump_db(dbA) |  | ||||||
|         del dbA |  | ||||||
| 
 |  | ||||||
|         # make sure the upgrades got committed to disk |  | ||||||
|         dbB = get_db(fn, TARGET_VERSION) |  | ||||||
|         dbB_text = dump_db(dbB) |  | ||||||
|         del dbB |  | ||||||
|         self.assertEqual(dbA_text, dbB_text) |  | ||||||
| 
 |  | ||||||
|         # The upgraded schema should be equivalent to that of a new DB. |  | ||||||
|         # However a text dump will differ because ALTER TABLE always appends |  | ||||||
|         # the new column to the end of a table, whereas our schema puts it |  | ||||||
|         # somewhere in the middle (wherever it fits naturally). Also ALTER |  | ||||||
|         # TABLE doesn't include comments. |  | ||||||
|         if False: |  | ||||||
|             latest_db = get_db(":memory:", TARGET_VERSION) |  | ||||||
|             latest_text = dump_db(latest_db) |  | ||||||
|             with open("up.sql","w") as f: f.write(dbA_text) |  | ||||||
|             with open("new.sql","w") as f: f.write(latest_text) |  | ||||||
|             # check with "diff -u _trial_temp/up.sql _trial_temp/new.sql" |  | ||||||
|             self.assertEqual(dbA_text, latest_text) |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user