]>
Commit | Line | Data |
---|---|---|
1 | #!/usr/bin/python | |
2 | ||
3 | import datetime | |
4 | import logging | |
5 | import re | |
6 | import json | |
7 | import tornado.web | |
8 | ||
9 | from .. import fireinfo | |
10 | ||
11 | from . import auth | |
12 | from . import base | |
13 | from . import ui_modules | |
14 | ||
15 | class BaseHandler(auth.CacheMixin, base.BaseHandler): | |
16 | @property | |
17 | def when(self): | |
18 | return self.get_argument_date("when", None) | |
19 | ||
20 | ||
21 | MIN_PROFILE_VERSION = 0 | |
22 | MAX_PROFILE_VERSION = 0 | |
23 | ||
24 | class Profile(dict): | |
25 | def __getattr__(self, key): | |
26 | try: | |
27 | return self[key] | |
28 | except KeyError: | |
29 | raise AttributeError(key) | |
30 | ||
31 | def __setattr__(self, key, val): | |
32 | self[key] = val | |
33 | ||
34 | ||
35 | class ProfileSendHandler(BaseHandler): | |
36 | def check_xsrf_cookie(self): | |
37 | # This cookie is not required here. | |
38 | pass | |
39 | ||
40 | def prepare(self): | |
41 | # Create an empty profile. | |
42 | self.profile = Profile() | |
43 | ||
44 | def __check_attributes(self, profile): | |
45 | """ | |
46 | Check for attributes that must be provided, | |
47 | """ | |
48 | attributes = ( | |
49 | "private_id", | |
50 | "profile_version", | |
51 | "public_id", | |
52 | ) | |
53 | for attr in attributes: | |
54 | if attr not in profile: | |
55 | raise tornado.web.HTTPError(400, "Profile lacks '%s' attribute: %s" % (attr, profile)) | |
56 | ||
57 | def __check_valid_ids(self, profile): | |
58 | """ | |
59 | Check if IDs contain valid data. | |
60 | """ | |
61 | for id in ("public_id", "private_id"): | |
62 | if re.match(r"^([a-f0-9]{40})$", "%s" % profile[id]) is None: | |
63 | raise tornado.web.HTTPError(400, "ID '%s' has wrong format: %s" % (id, profile)) | |
64 | ||
65 | def __check_equal_ids(self, profile): | |
66 | """ | |
67 | Check if public_id and private_id are equal. | |
68 | """ | |
69 | if profile.public_id == profile.private_id: | |
70 | raise tornado.web.HTTPError(400, "Public and private IDs are equal: %s" % profile) | |
71 | ||
72 | def __check_matching_ids(self, profile): | |
73 | """ | |
74 | Check if a profile with the given public_id is already in the | |
75 | database. If so we need to check if the private_id matches. | |
76 | """ | |
77 | p = self.profiles.find_one({ "public_id" : profile["public_id"]}) | |
78 | if not p: | |
79 | return | |
80 | ||
81 | p = Profile(p) | |
82 | if p.private_id != profile.private_id: | |
83 | raise tornado.web.HTTPError(400, "Mismatch of private_id: %s" % profile) | |
84 | ||
85 | def __check_profile_version(self, profile): | |
86 | """ | |
87 | Check if this version of the server software does support the | |
88 | received profile. | |
89 | """ | |
90 | version = profile.profile_version | |
91 | ||
92 | if version < MIN_PROFILE_VERSION or version > MAX_PROFILE_VERSION: | |
93 | raise tornado.web.HTTPError(400, | |
94 | "Profile version is not supported: %s" % version) | |
95 | ||
96 | def check_profile_blob(self, profile): | |
97 | """ | |
98 | This method checks if the blob is sane. | |
99 | """ | |
100 | checks = ( | |
101 | self.__check_attributes, | |
102 | self.__check_valid_ids, | |
103 | self.__check_equal_ids, | |
104 | self.__check_profile_version, | |
105 | # These checks require at least one database query and should be done | |
106 | # at last. | |
107 | self.__check_matching_ids, | |
108 | ) | |
109 | ||
110 | for check in checks: | |
111 | check(profile) | |
112 | ||
113 | # If we got here, everything is okay and we can go on... | |
114 | ||
115 | def get_profile_blob(self): | |
116 | profile = self.get_argument("profile", None) | |
117 | ||
118 | # Send "400 bad request" if no profile was provided | |
119 | if not profile: | |
120 | raise tornado.web.HTTPError(400, "No profile received") | |
121 | ||
122 | # Try to decode the profile. | |
123 | try: | |
124 | return json.loads(profile) | |
125 | except json.decoder.JSONDecodeError as e: | |
126 | raise tornado.web.HTTPError(400, "Profile could not be decoded: %s" % e) | |
127 | ||
128 | # The GET method is only allowed in debugging mode. | |
129 | def get(self, public_id): | |
130 | if not self.application.settings["debug"]: | |
131 | raise tornado.web.HTTPError(405) | |
132 | ||
133 | return self.post(public_id) | |
134 | ||
135 | def post(self, public_id): | |
136 | profile_blob = self.get_profile_blob() | |
137 | #self.check_profile_blob(profile_blob) | |
138 | ||
139 | # Handle the profile. | |
140 | with self.db.transaction(): | |
141 | try: | |
142 | self.fireinfo.handle_profile(public_id, profile_blob, | |
143 | country_code=self.current_country_code) | |
144 | ||
145 | except fireinfo.ProfileParserError as e: | |
146 | raise tornado.web.HTTPError(400, "Could not parse profile: %s" % e) | |
147 | ||
148 | self.finish("Your profile was successfully saved to the database.") | |
149 | ||
150 | ||
151 | class IndexHandler(BaseHandler): | |
152 | def get(self): | |
153 | data = { | |
154 | # Release | |
155 | "latest_release" : self.backend.releases.get_latest(), | |
156 | ||
157 | # Hardware | |
158 | "arches" : self.fireinfo.get_arch_map(when=self.when), | |
159 | "cpu_vendors" : self.fireinfo.get_cpu_vendors_map(when=self.when), | |
160 | "memory_avg" : self.backend.fireinfo.get_average_memory_amount(when=self.when), | |
161 | ||
162 | # Virtualization | |
163 | "hypervisors" : self.fireinfo.get_hypervisor_map(when=self.when), | |
164 | "virtual_ratio" : self.fireinfo.get_virtual_ratio(when=self.when), | |
165 | ||
166 | # Location | |
167 | "locations" : self.fireinfo.get_geo_location_map(when=self.when), | |
168 | } | |
169 | ||
170 | self.render("fireinfo/index.html", **data) | |
171 | ||
172 | ||
173 | class DriverDetail(BaseHandler): | |
174 | def get(self, driver): | |
175 | self.render("fireinfo/driver.html", driver=driver, | |
176 | driver_map=self.fireinfo.get_driver_map(driver, when=self.when)) | |
177 | ||
178 | ||
179 | class ProfileHandler(BaseHandler): | |
180 | def get(self, profile_id): | |
181 | profile = self.fireinfo.get_profile(profile_id, when=self.when) | |
182 | ||
183 | if not profile or not profile.is_showable(): | |
184 | raise tornado.web.HTTPError(404) | |
185 | ||
186 | self.render("fireinfo/profile.html", profile=profile) | |
187 | ||
188 | ||
189 | class RandomProfileHandler(BaseHandler): | |
190 | def get(self): | |
191 | profile_id = self.fireinfo.get_random_profile(when=self.when) | |
192 | if profile_id is None: | |
193 | raise tornado.web.HTTPError(404) | |
194 | ||
195 | self.redirect("/profile/%s" % profile_id) | |
196 | ||
197 | ||
198 | class ReleasesHandler(BaseHandler): | |
199 | def get(self): | |
200 | data = { | |
201 | "releases" : self.fireinfo.get_releases_map(when=self.when), | |
202 | "kernels" : self.fireinfo.get_kernels_map(when=self.when), | |
203 | } | |
204 | ||
205 | return self.render("fireinfo/releases.html", **data) | |
206 | ||
207 | ||
208 | class ProcessorsHandler(BaseHandler): | |
209 | def get(self): | |
210 | flags = {} | |
211 | ||
212 | for platform in ("arm", "x86"): | |
213 | flags[platform] = \ | |
214 | self.fireinfo.get_common_cpu_flags_by_platform(platform, when=self.when) | |
215 | ||
216 | return self.render("fireinfo/processors.html", flags=flags) | |
217 | ||
218 | ||
219 | class VendorsHandler(BaseHandler): | |
220 | def get(self): | |
221 | vendors = self.fireinfo.get_vendor_list(when=self.when) | |
222 | ||
223 | self.render("fireinfo/vendors.html", vendors=vendors) | |
224 | ||
225 | ||
226 | class VendorHandler(BaseHandler): | |
227 | def get(self, subsystem, vendor_id): | |
228 | devices = self.fireinfo.get_devices_by_vendor(subsystem, vendor_id, when=self.when) | |
229 | if not devices: | |
230 | raise tornado.web.HTTPError(404) | |
231 | ||
232 | vendor_name = self.fireinfo.get_vendor_string(subsystem, vendor_id) | |
233 | ||
234 | self.render("fireinfo/vendor.html", vendor_name=vendor_name, devices=devices) | |
235 | ||
236 | ||
237 | class DeviceTableModule(ui_modules.UIModule): | |
238 | def render(self, devices, show_group=True, embedded=False): | |
239 | return self.render_string("fireinfo/modules/table-devices.html", | |
240 | devices=devices, show_group=show_group, embedded=embedded) | |
241 | ||
242 | ||
243 | class DeviceAndGroupsTableModule(ui_modules.UIModule): | |
244 | def render(self, devices): | |
245 | _ = self.locale.translate | |
246 | ||
247 | groups = {} | |
248 | ||
249 | for device in devices: | |
250 | if device.cls not in groups: | |
251 | groups[device.cls] = [] | |
252 | ||
253 | groups[device.cls].append(device) | |
254 | ||
255 | # Sort all devices | |
256 | for key in list(groups.keys()): | |
257 | groups[key].sort() | |
258 | ||
259 | # Order the groups by their name | |
260 | groups = list(groups.items()) | |
261 | groups.sort() | |
262 | ||
263 | return self.render_string("fireinfo/modules/table-devices-and-groups.html", | |
264 | groups=groups) | |
265 | ||
266 | ||
267 | class AdminIndexHandler(BaseHandler): | |
268 | @tornado.web.authenticated | |
269 | def prepare(self): | |
270 | if not self.current_user.is_staff(): | |
271 | raise tornado.web.HTTPError(401) | |
272 | ||
273 | @tornado.web.authenticated | |
274 | def get(self): | |
275 | with_data, total = self.backend.fireinfo.get_active_profiles() | |
276 | ||
277 | self.render("fireinfo/admin.html", with_data=with_data, total=total) |