]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/introweb.py
new introducer: signed extensible dictionary-based messages! refs #466
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / introweb.py
1
2 import time, os
3 from nevow import rend, inevow
4 from nevow.static import File as nevow_File
5 from nevow.util import resource_filename
6 from foolscap.api import SturdyRef
7 from twisted.internet import address
8 import allmydata
9 import simplejson
10 from allmydata import get_package_versions_string
11 from allmydata.util import idlib
12 from allmydata.web.common import getxmlfile, get_arg
13
14 class IntroducerRoot(rend.Page):
15
16     addSlash = True
17     docFactory = getxmlfile("introducer.xhtml")
18
19     child_operations = None
20
21     def __init__(self, introducer_node):
22         self.introducer_node = introducer_node
23         self.introducer_service = introducer_node.getServiceNamed("introducer")
24         rend.Page.__init__(self, introducer_node)
25         static_dir = resource_filename("allmydata.web", "static")
26         for filen in os.listdir(static_dir):
27             self.putChild(filen, nevow_File(os.path.join(static_dir, filen)))
28
29     def renderHTTP(self, ctx):
30         t = get_arg(inevow.IRequest(ctx), "t")
31         if t == "json":
32             return self.render_JSON(ctx)
33         return rend.Page.renderHTTP(self, ctx)
34
35     def render_JSON(self, ctx):
36         res = {}
37
38         counts = {}
39         subscribers = self.introducer_service.get_subscribers()
40         for (service_name, ign, ign, ign) in subscribers:
41             if service_name not in counts:
42                 counts[service_name] = 0
43             counts[service_name] += 1
44         res["subscription_summary"] = counts
45
46         announcement_summary = {}
47         service_hosts = {}
48         for a in self.introducer_service.get_announcements().values():
49             (_, _, ann, when) = a
50             service_name = ann["service-name"]
51             if service_name not in announcement_summary:
52                 announcement_summary[service_name] = 0
53             announcement_summary[service_name] += 1
54             if service_name not in service_hosts:
55                 service_hosts[service_name] = set()
56             # it's nice to know how many distinct hosts are available for
57             # each service. We define a "host" by a set of addresses
58             # (hostnames or ipv4 addresses), which we extract from the
59             # connection hints. In practice, this is usually close
60             # enough: when multiple services are run on a single host,
61             # they're usually either configured with the same addresses,
62             # or setLocationAutomatically picks up the same interfaces.
63             furl = ann["anonymous-storage-FURL"]
64             locations = SturdyRef(furl).getTubRef().getLocations()
65             # list of tuples, ("ipv4", host, port)
66             host = frozenset([hint[1]
67                               for hint in locations
68                               if hint[0] == "ipv4"])
69             service_hosts[service_name].add(host)
70         res["announcement_summary"] = announcement_summary
71         distinct_hosts = dict([(name, len(hosts))
72                                for (name, hosts)
73                                in service_hosts.iteritems()])
74         res["announcement_distinct_hosts"] = distinct_hosts
75
76         return simplejson.dumps(res, indent=1) + "\n"
77
78     # FIXME: This code is duplicated in root.py and introweb.py.
79     def data_version(self, ctx, data):
80         return get_package_versions_string()
81     def data_import_path(self, ctx, data):
82         return str(allmydata).replace("/", "/ ") # XXX kludge for wrapping
83     def data_my_nodeid(self, ctx, data):
84         return idlib.nodeid_b2a(self.introducer_node.nodeid)
85
86     def render_announcement_summary(self, ctx, data):
87         services = {}
88         for a in self.introducer_service.get_announcements().values():
89             (_, _, ann, when) = a
90             service_name = ann["service-name"]
91             if service_name not in services:
92                 services[service_name] = 0
93             services[service_name] += 1
94         service_names = services.keys()
95         service_names.sort()
96         return ", ".join(["%s: %d" % (service_name, services[service_name])
97                           for service_name in service_names])
98
99     def render_client_summary(self, ctx, data):
100         counts = {}
101         clients = self.introducer_service.get_subscribers()
102         for (service_name, ign, ign, ign) in clients:
103             if service_name not in counts:
104                 counts[service_name] = 0
105             counts[service_name] += 1
106         return ", ".join([ "%s: %d" % (name, counts[name])
107                            for name in sorted(counts.keys()) ] )
108
109     def data_services(self, ctx, data):
110         introsvc = self.introducer_service
111         services = []
112         for a in introsvc.get_announcements().values():
113             (_, _, ann, when) = a
114             if ann["service-name"] == "stub_client":
115                 continue
116             services.append( (when, ann) )
117         services.sort(key=lambda x: (x[1]["service-name"], x[1]["nickname"]))
118         # this used to be:
119         #services.sort(lambda a,b: cmp( (a[1][1], a), (b[1][1], b) ) )
120         # service_name was the primary key, then the whole tuple (starting
121         # with the furl) was the secondary key
122         return services
123
124     def render_service_row(self, ctx, (since,ann)):
125         sr = SturdyRef(ann["anonymous-storage-FURL"])
126         nodeid = sr.tubID
127         advertised = self.show_location_hints(sr)
128         ctx.fillSlots("peerid", nodeid)
129         ctx.fillSlots("nickname", ann["nickname"])
130         ctx.fillSlots("advertised", " ".join(advertised))
131         ctx.fillSlots("connected", "?")
132         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
133         ctx.fillSlots("announced",
134                       time.strftime(TIME_FORMAT, time.localtime(since)))
135         ctx.fillSlots("version", ann["my-version"])
136         ctx.fillSlots("service_name", ann["service-name"])
137         return ctx.tag
138
139     def data_subscribers(self, ctx, data):
140         return self.introducer_service.get_subscribers()
141
142     def render_subscriber_row(self, ctx, s):
143         (service_name, since, info, rref) = s
144         nickname = info.get("nickname", "?")
145         version = info.get("my-version", "?")
146
147         sr = rref.getSturdyRef()
148         # if the subscriber didn't do Tub.setLocation, nodeid will be None
149         nodeid = sr.tubID or "?"
150         ctx.fillSlots("peerid", nodeid)
151         ctx.fillSlots("nickname", nickname)
152         advertised = self.show_location_hints(sr)
153         ctx.fillSlots("advertised", " ".join(advertised))
154         remote_host = rref.tracker.broker.transport.getPeer()
155         if isinstance(remote_host, address.IPv4Address):
156             remote_host_s = "%s:%d" % (remote_host.host, remote_host.port)
157         else:
158             # loopback is a non-IPv4Address
159             remote_host_s = str(remote_host)
160         ctx.fillSlots("connected", remote_host_s)
161         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
162         ctx.fillSlots("since",
163                       time.strftime(TIME_FORMAT, time.localtime(since)))
164         ctx.fillSlots("version", version)
165         ctx.fillSlots("service_name", service_name)
166         return ctx.tag
167
168     def show_location_hints(self, sr, ignore_localhost=True):
169         advertised = []
170         for hint in sr.locationHints:
171             if isinstance(hint, str):
172                 # Foolscap-0.2.5 and earlier used strings in .locationHints
173                 if ignore_localhost and hint.startswith("127.0.0.1"):
174                     continue
175                 advertised.append(hint.split(":")[0])
176             else:
177                 # Foolscap-0.2.6 and later use tuples of ("ipv4", host, port)
178                 if hint[0] == "ipv4":
179                     host = hint[1]
180                 if ignore_localhost and host == "127.0.0.1":
181                     continue
182                 advertised.append(hint[1])
183         return advertised
184
185