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