]> git.ipfire.org Git - pbs.git/blob - src/web/builders.py
builders: Add a new stats handler
[pbs.git] / src / web / builders.py
1 #!/usr/bin/python
2
3 import logging
4 import tornado.web
5
6 from . import base
7 from . import ui_modules
8
9 # Setup logging
10 log = logging.getLogger("pbs.web.builders")
11
12 class APIv1ControlHandler(base.APIMixin, tornado.websocket.WebSocketHandler):
13 @tornado.web.authenticated
14 async def open(self):
15 # The builder has opened a new connection
16 self.current_user.connected(self)
17
18 # After the builder has connected, try to dispatch some jobs
19 await self.backend.jobs.queue.dispatch()
20
21 def on_ping(self, data):
22 log.debug("%s has pinged us" % self.builder)
23
24 def on_close(self):
25 # Drop the connection to the builder
26 self.current_user.disconnected()
27
28 async def on_message(self, message):
29 # Decode message
30 message = self._decode_json_message(message)
31
32 # Fetch the message type & data
33 type = message.get("type")
34 data = message.get("data")
35
36 # Handle stats
37 if type == "stats":
38 await self._handle_stats(data)
39
40 # Log an error and ignore any other messages
41 else:
42 log.error("Received message of type '%s' which we cannot handle here" % type)
43
44 async def _handle_stats(self, data):
45 """
46 Handles stats messages
47 """
48 with self.db.transaction():
49 await self.builder.log_stats(**data)
50
51
52 class APIv1StatsHandler(base.APIMixin, base.BaseHandler):
53 @base.negotiate
54 async def post(self):
55 stats = {
56 # CPU info
57 "cpu_model" : self.get_argument("cpu_model"),
58 "cpu_count" : self.get_argument_int("cpu_count"),
59 "cpu_arch" : self.get_argument("cpu_arch"),
60
61 # Pakfire Version
62 "pakfire_version" : self.get_argument("pakfire_version"),
63
64 # OS
65 "os_name" : self.get_argument("os_name"),
66
67 # CPU stats
68 "cpu_user" : self.get_argument_float("cpu_user"),
69 "cpu_nice" : self.get_argument_float("cpu_nice"),
70 "cpu_system" : self.get_argument_float("cpu_system"),
71 "cpu_idle" : self.get_argument_float("cpu_idle"),
72 "cpu_iowait" : self.get_argument_float("cpu_iowait"),
73 "cpu_irq" : self.get_argument_float("cpu_irq"),
74 "cpu_softirq" : self.get_argument_float("cpu_softirq"),
75 "cpu_steal" : self.get_argument_float("cpu_steal"),
76 "cpu_guest" : self.get_argument_float("cpu_guest"),
77 "cpu_guest_nice" : self.get_argument_float("cpu_guest_nice"),
78
79 # Load Average
80 "loadavg1" : self.get_argument_float("loadavg1"),
81 "loadavg5" : self.get_argument_float("loadavg5"),
82 "loadavg15" : self.get_argument_float("loadavg15"),
83
84 # Memory
85 "mem_total" : self.get_argument_int("mem_total"),
86 "mem_available" : self.get_argument_int("mem_available"),
87 "mem_used" : self.get_argument_int("mem_used"),
88 "mem_free" : self.get_argument_int("mem_free"),
89 "mem_active" : self.get_argument_int("mem_active"),
90 "mem_inactive" : self.get_argument_int("mem_inactive"),
91 "mem_buffers" : self.get_argument_int("mem_buffers"),
92 "mem_cached" : self.get_argument_int("mem_cached"),
93 "mem_shared" : self.get_argument_int("mem_shared"),
94
95 # Swap
96 "swap_total" : self.get_argument_int("swap_total"),
97 "swap_used" : self.get_argument_int("swap_used"),
98 "swap_free" : self.get_argument_int("swap_free"),
99 }
100
101 with self.db.transaction():
102 await self.builder.log_stats(**stats)
103
104 # Send OK
105 self.finish({})
106
107
108 class StatsHandler(base.BaseHandler, tornado.websocket.WebSocketHandler):
109 # No authentication required
110 async def open(self, name):
111 builder = self.backend.builders.get_by_name(name)
112 if not builder:
113 raise tornado.web.HTTPError(404, "Could not find builder %s" % name)
114
115 # Register to receive updates
116 self.backend.builders.stats.join(builder=builder, connection=self)
117
118 # Initially send the stats that we currently have
119 if builder.stats:
120 await self.submit_stats(builder.stats)
121
122 def on_close(self):
123 self.backend.builders.stats.leave(self)
124
125 async def submit_stats(self, stats):
126 await self.write_message({
127 "cpu_usage" : stats.cpu_usage,
128 "mem_usage" : stats.mem_usage,
129 "swap_usage" : stats.swap_usage,
130 })
131
132
133 class IndexHandler(base.BaseHandler):
134 def get(self):
135 self.render("builders/index.html", builders=self.backend.builders)
136
137
138 class ShowHandler(base.BaseHandler):
139 async def get(self, hostname):
140 builder = self.backend.builders.get_by_name(hostname)
141 if not builder:
142 raise tornado.web.HTTPError(404, "Could not find builder %s" % hostname)
143
144 # Fetch status
145 args = {
146 "is_running" : await builder.is_running(),
147 "is_shutting_down" : await builder.is_shutting_down(),
148 "is_shut_down" : await builder.is_shut_down(),
149 }
150
151 self.render("builders/show.html", builder=builder, **args)
152
153
154 class CreateHandler(base.BaseHandler):
155 @tornado.web.authenticated
156 def get(self):
157 # Must be admin
158 if not self.current_user.is_admin():
159 raise tornado.web.HTTPError(403)
160
161 self.render("builders/create.html")
162
163 @tornado.web.authenticated
164 def post(self):
165 # Must be admin
166 if not self.current_user.is_admin():
167 raise tornado.web.HTTPError(403)
168
169 hostname = self.get_argument("hostname")
170
171 # Create a new builder
172 with self.db.transaction():
173 builder = self.backend.builders.create(hostname, user=self.current_user)
174
175 self.redirect("/builders/%s/edit" % builder.hostname)
176
177
178 class BuilderEditHandler(base.BaseHandler):
179 @tornado.web.authenticated
180 def get(self, hostname):
181 builder = self.backend.builders.get_by_name(hostname)
182 if not builder:
183 raise tornado.web.HTTPError(404, "Builder not found")
184
185 # Check permissions
186 if not builder.has_perm(self.current_user):
187 raise tornado.web.HTTPError(403)
188
189 self.render("builders/edit.html", builder=builder)
190
191 @tornado.web.authenticated
192 async def post(self, hostname):
193 builder = self.backend.builders.get_by_name(hostname)
194 if not builder:
195 raise tornado.web.HTTPError(404, "Builder not found: %s" % hostname)
196
197 # Check permissions
198 if not builder.has_perm(self.current_user):
199 raise tornado.web.HTTPError(403)
200
201 with self.db.transaction():
202 builder.enabled = self.get_argument("enabled", False)
203 builder.maintenance = self.get_argument_bool("maintenance")
204 builder.max_jobs = self.get_argument_int("max_jobs")
205
206 # Try to dispatch more jobs
207 await self.backend.jobs.queue.dispatch()
208
209 self.redirect("/builders/%s" % builder.hostname)
210
211
212 class DeleteHandler(base.BaseHandler):
213 @tornado.web.authenticated
214 def get(self, name):
215 builder = self.backend.builders.get_by_name(name)
216 if not builder:
217 raise tornado.web.HTTPError(404, "Builder not found: %s" % name)
218
219 # Check permissions
220 if not builder.has_perm(self.current_user):
221 raise tornado.web.HTTPError(403)
222
223 self.render("builders/delete.html", builder=builder)
224
225 @tornado.web.authenticated
226 async def post(self, hostname):
227 builder = self.backend.builders.get_by_name(hostname)
228 if not builder:
229 raise tornado.web.HTTPError(404, "Builder not found: %s" % hostname)
230
231 # Check permissions
232 if not builder.has_perm(self.current_user):
233 raise tornado.web.HTTPError(403)
234
235 # Delete the builder
236 with self.db.transaction():
237 builder.delete(user=self.current_user)
238
239 self.redirect("/builders")
240
241
242 class StartHandler(base.BaseHandler):
243 @tornado.web.authenticated
244 def get(self, name):
245 builder = self.backend.builders.get_by_name(name)
246 if not builder:
247 raise tornado.web.HTTPError(404, "Builder not found: %s" % name)
248
249 # Check permissions
250 if not builder.has_perm(self.current_user):
251 raise tornado.web.HTTPError(403)
252
253 # Builders must be in maintenance mode
254 if not builder.maintenance:
255 raise tornado.web.HTTPError(400, "%s is not in maintenance mode" % builder)
256
257 self.render("builders/start.html", builder=builder)
258
259 @tornado.web.authenticated
260 async def post(self, name):
261 builder = self.backend.builders.get_by_name(name)
262 if not builder:
263 raise tornado.web.HTTPError(404, "Builder not found: %s" % name)
264
265 # Check permissions
266 if not builder.has_perm(self.current_user):
267 raise tornado.web.HTTPError(403)
268
269 # Builders must be in maintenance mode
270 if not builder.maintenance:
271 raise tornado.web.HTTPError(400, "%s is not in maintenance mode" % builder)
272
273 # Start the builder
274 try:
275 await builder.start(wait=False)
276
277 # XXX what do we do when this fails?
278 except:
279 raise
280
281 self.redirect("/builders/%s" % builder.hostname)
282
283
284 class StopHandler(base.BaseHandler):
285 @tornado.web.authenticated
286 def get(self, name):
287 builder = self.backend.builders.get_by_name(name)
288 if not builder:
289 raise tornado.web.HTTPError(404, "Builder not found: %s" % name)
290
291 # Check permissions
292 if not builder.has_perm(self.current_user):
293 raise tornado.web.HTTPError(403)
294
295 # Builders must be in maintenance mode
296 if not builder.maintenance:
297 raise tornado.web.HTTPError(400, "%s is not in maintenance mode" % builder)
298
299 self.render("builders/stop.html", builder=builder)
300
301 @tornado.web.authenticated
302 async def post(self, name):
303 builder = self.backend.builders.get_by_name(name)
304 if not builder:
305 raise tornado.web.HTTPError(404, "Builder not found: %s" % name)
306
307 # Check permissions
308 if not builder.has_perm(self.current_user):
309 raise tornado.web.HTTPError(403)
310
311 # Builders must be in maintenance mode
312 if not builder.maintenance:
313 raise tornado.web.HTTPError(400, "%s is not in maintenance mode" % builder)
314
315 # Stop the builder
316 try:
317 await builder.stop(wait=False)
318
319 # XXX what do we do when this fails?
320 except:
321 raise
322
323 self.redirect("/builders/%s" % builder.hostname)
324
325
326 class StatsModule(ui_modules.UIModule):
327 def render(self, builder):
328 return self.render_string("builders/modules/stats.html", builder=builder)
329
330 def javascript_files(self):
331 return (
332 "js/builders-stats.min.js",
333 )