]> git.ipfire.org Git - people/shoehn/ipfire.org.git/blob - webapp/handlers_stasy.py
Re-add disabled code.
[people/shoehn/ipfire.org.git] / webapp / handlers_stasy.py
1 #!/usr/bin/python
2
3 from __future__ import division
4
5 import datetime
6 import hwdata
7 import ipaddr
8 import logging
9 import re
10 import simplejson
11 import tornado.web
12
13 import backend
14
15 from handlers_base import *
16
17 class StasyBaseHandler(BaseHandler):
18 def format_size(self, s):
19 units = ("K", "M", "G", "T")
20 unit = 0
21
22 while s >= 1024 and unit < len(units):
23 s /= 1024
24 unit += 1
25
26 return "%.1f%s" % (s, units[unit])
27
28 def cut_string(self, s, limit=15):
29 if len(s) > limit:
30 s = s[:limit] + "..."
31
32 return s
33
34 def render(self, *args, **kwargs):
35 kwargs.update({
36 "cut_string" : self.cut_string,
37 "format_size" : self.format_size,
38 })
39
40 return BaseHandler.render(self, *args, **kwargs)
41
42
43 MIN_PROFILE_VERSION = 0
44 MAX_PROFILE_VERSION = 0
45
46 class Profile(dict):
47 def __getattr__(self, key):
48 try:
49 return self[key]
50 except KeyError:
51 raise AttributeError, key
52
53 def __setattr__(self, key, val):
54 self[key] = val
55
56
57 class StasyProfileSendHandler(StasyBaseHandler):
58 def check_xsrf_cookie(self):
59 # This cookie is not required here.
60 pass
61
62 @property
63 def archives(self):
64 return self.stasy.archives
65
66 @property
67 def profiles(self):
68 return self.stasy.profiles
69
70 def prepare(self):
71 # Create an empty profile.
72 self.profile = Profile()
73
74 def __check_attributes(self, profile):
75 """
76 Check for attributes that must be provided,
77 """
78
79 attributes = (
80 "private_id",
81 "profile_version",
82 "public_id",
83 "updated",
84 )
85 for attr in attributes:
86 if not profile.has_key(attr):
87 raise tornado.web.HTTPError(400, "Profile lacks '%s' attribute: %s" % (attr, profile))
88
89 def __check_valid_ids(self, profile):
90 """
91 Check if IDs contain valid data.
92 """
93
94 for id in ("public_id", "private_id"):
95 if re.match(r"^([a-f0-9]{40})$", "%s" % profile[id]) is None:
96 raise tornado.web.HTTPError(400, "ID '%s' has wrong format: %s" % (id, profile))
97
98 def __check_equal_ids(self, profile):
99 """
100 Check if public_id and private_id are equal.
101 """
102
103 if profile.public_id == profile.private_id:
104 raise tornado.web.HTTPError(400, "Public and private IDs are equal: %s" % profile)
105
106 def __check_matching_ids(self, profile):
107 """
108 Check if a profile with the given public_id is already in the
109 database. If so we need to check if the private_id matches.
110 """
111 p = self.profiles.find_one({ "public_id" : profile["public_id"]})
112 if not p:
113 return
114
115 p = Profile(p)
116 if p.private_id != profile.private_id:
117 raise tornado.web.HTTPError(400, "Mismatch of private_id: %s" % profile)
118
119 def __check_profile_version(self, profile):
120 """
121 Check if this version of the server software does support the
122 received profile.
123 """
124 version = profile.profile_version
125
126 if version < MIN_PROFILE_VERSION or version > MAX_PROFILE_VERSION:
127 raise tornado.web.HTTPError(400,
128 "Profile version is not supported: %s" % version)
129
130 def check_profile(self):
131 """
132 This method checks if the blob is sane.
133 """
134
135 checks = (
136 self.__check_attributes,
137 self.__check_valid_ids,
138 self.__check_equal_ids,
139 self.__check_profile_version,
140 # These checks require at least one database query and should be done
141 # at last.
142 self.__check_matching_ids,
143 )
144
145 for check in checks:
146 check(self.profile)
147
148 # If we got here, everything is okay and we can go on...
149
150 # The GET method is only allowed in debugging mode.
151 def get(self, public_id):
152 if not self.application.settings["debug"]:
153 return tornado.web.HTTPError(405)
154
155 return self.post(public_id)
156
157 def post(self, public_id):
158 profile = self.get_argument("profile", None)
159
160 # Send "400 bad request" if no profile was provided
161 if not profile:
162 raise tornado.web.HTTPError(400, "No profile received.")
163
164 # Try to decode the profile.
165 try:
166 self.profile.update(simplejson.loads(profile))
167 except simplejson.decoder.JSONDecodeError, e:
168 raise tornado.web.HTTPError(400, "Profile could not be decoded: %s" % e)
169
170 # Create a shortcut and overwrite public_id from query string
171 profile = self.profile
172 profile.public_id = public_id
173
174 # Add timestamp to the profile
175 profile.updated = datetime.datetime.utcnow()
176
177 # Check if profile contains proper data.
178 self.check_profile()
179
180 # Get GeoIP information if address is not defined in rfc1918
181 remote_ips = self.request.remote_ip.split(", ")
182 for remote_ip in remote_ips:
183 try:
184 addr = ipaddr.IPAddress(remote_ip)
185 except ValueError:
186 # Skip invalid IP addresses.
187 continue
188
189 # Check if the given IP address is from a
190 # private network.
191 if addr.is_private:
192 continue
193
194 location = self.geoip.get_location(remote_ip)
195 if location:
196 profile.geoip = {
197 "country_code" : location.country,
198 }
199 else:
200 profile.geoip = None
201
202 break
203
204 # Move previous profiles to archive and keep only the latest one
205 # in profiles. This will make full table lookups faster.
206 self.stasy.move_profiles({ "public_id" : profile.public_id })
207
208 # Write profile to database
209 id = self.profiles.save(profile)
210
211 self.write("Your profile was successfully saved to the database.")
212 self.finish()
213
214 logging.debug("Saved profile: %s" % profile)
215
216 def on_finish(self):
217 logging.debug("Starting automatic cleanup...")
218
219 # Remove all profiles that were not updated since 4 weeks.
220 not_updated_since = datetime.datetime.utcnow() - \
221 datetime.timedelta(weeks=4)
222
223 self.stasy.move_profiles({ "updated" : { "$lt" : not_updated_since }})
224
225
226 class StasyIndexHandler(StasyBaseHandler):
227 def _profile_not_found(self, profile_id):
228 self.set_status(404)
229 self.render("fireinfo/profile-notfound.html", profile_id=profile_id)
230
231 def get(self):
232 self.render("fireinfo/index.html")
233
234 def post(self):
235 profile_id = self.get_argument("profile_id", None)
236 if not profile_id:
237 raise tornado.web.HTTPError(400, "No profile ID was given.")
238
239 if not self.stasy.profile_exists(profile_id):
240 self._profile_not_found(profile_id)
241 return
242
243 self.redirect("/profile/%s" % profile_id)
244
245
246 class StasyProfileDetailHandler(StasyIndexHandler):
247 def get(self, profile_id):
248 profile = self.stasy.get_profile(profile_id)
249 if not profile:
250 self._profile_not_found(profile_id)
251 return
252
253 self.render("fireinfo/profile-detail.html", profile=profile)
254
255
256 class StasyStatsHandler(StasyBaseHandler):
257 def get(self):
258 self.render("fireinfo/stats.html")
259
260
261 class StasyStatsCPUHandler(StasyBaseHandler):
262 def get(self):
263 return self.render("fireinfo/stats-cpus.html",
264 cpu_vendors=self.stasy.cpu_vendors_map,
265 average_speed=self.stasy.cpu_speed_average,
266 cpu_speeds=self.stasy.cpu_speed_map,
267 cpu_cores=self.stasy.get_cpu_cores_map(),
268 bogomips=self.stasy.get_cpu_bogomips_accumulated())
269
270
271 class StasyStatsCPUFlagsHandler(StasyBaseHandler):
272 def get(self):
273 kwargs = {}
274
275 flags = (
276 ("lm", "lm"),
277 ("pae", "pae"),
278 ("virt", ("vmx", "svm")),
279 )
280
281 for name, flag in flags:
282 kwargs["cpus_" + name] = self.stasy.get_cpu_flag_map(flag)
283
284 return self.render("fireinfo/stats-cpu-flags.html", **kwargs)
285
286 class StasyStatsMemoryHandler(StasyBaseHandler):
287 def get(self):
288 return self.render("fireinfo/stats-memory.html",
289 average_memory=self.stasy.memory_average,
290 memory=self.stasy.get_memory_map())
291
292
293 class StasyStatsOSesHandler(StasyBaseHandler):
294 def get(self):
295 return self.render("fireinfo/stats-oses.html",
296 arches=self.stasy.arch_map,
297 kernels=self.stasy.kernel_map,
298 releases=self.stasy.release_map)
299
300
301 class StasyStatsVirtualHandler(StasyBaseHandler):
302 def get(self):
303 return self.render("fireinfo/stats-virtual.html",
304 hypervisor_vendors = self.stasy.hypervisor_map,
305 is_virtual = self.stasy.virtual_map)
306
307 class StasyStatsGeoHandler(StasyBaseHandler):
308 def get(self):
309 return self.render("fireinfo/stats-geo.html",
310 languages = self.stasy.get_language_map(),
311 geo_locations = self.stasy.get_geo_location_map())
312
313
314 class StasyStatsNetworkHandler(StasyBaseHandler):
315 def get(self):
316 return self.render("fireinfo/stats-network.html",
317 network_zones=self.stasy.get_network_zones_map())
318
319
320 class StasyStatsVendorDetail(StasyBaseHandler):
321 def get(self, bus, vendor_id):
322 # XXX some way ugly
323 bus2cls = {
324 "pci" : hwdata.PCI,
325 "usb" : hwdata.USB
326 }
327 cls = bus2cls[bus.lower()]
328 vendor_name = cls().get_vendor(vendor_id)
329
330 # Get a list of all models we know from this vendor
331 models = self.stasy.get_models_by_vendor(bus, vendor_id)
332
333 self.render("fireinfo/vendor-detail.html",
334 vendor_name=vendor_name, models=models)
335
336
337 class StasyStatsModelDetail(StasyBaseHandler):
338 def get(self, bus, vendor_id, model_id):
339 bus2cls = {
340 "pci" : hwdata.PCI,
341 "usb" : hwdata.USB
342 }
343
344 cls = bus2cls[bus.lower()]
345
346 vendor_name = cls().get_vendor(vendor_id)
347 model_name = cls().get_device(vendor_id, model_id)
348
349 percentage = \
350 self.stasy.get_device_percentage(bus, vendor_id, model_id) * 100
351
352 self.render("fireinfo/model-detail.html",
353 vendor_id=vendor_id,
354 vendor_name=vendor_name,
355 model_id=model_id,
356 model_name=model_name,
357 percentage=percentage,
358 bus=bus.lower())
359
360
361 class AdminFireinfoStatsHandler(StasyBaseHandler):
362 def get(self):
363 _ = self.locale.translate
364
365 data = {}
366
367 data["profiles_count"], data["profiles_count_all"] = \
368 self.stasy.get_profile_ratio()
369
370 data["archives_count"] = self.stasy.get_archives_count()
371
372 # updated since 24h
373 #since = { "hours" : 24 }
374 #updates = self.stasy.get_updates_by_release_since(since)
375 #updates[_("All versions")] = self.stasy.get_updated_since(since).count()
376 #data["updated_since_24h"] = updates
377
378 self.render("fireinfo/stats-admin.html", **data)
379