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