]>
Commit | Line | Data |
---|---|---|
f6e6ff79 MT |
1 | #!/usr/bin/python |
2 | ||
3 | import base64 | |
4 | import hashlib | |
c2902b29 | 5 | import json |
f6e6ff79 | 6 | import logging |
c2902b29 | 7 | import time |
f6e6ff79 | 8 | import tornado.web |
f6e6ff79 | 9 | |
2c909128 MT |
10 | from .. import builds |
11 | from .. import builders | |
12 | from .. import uploads | |
13 | from .. import users | |
14 | ||
c2902b29 MT |
15 | class LongPollMixin(object): |
16 | def initialize(self): | |
17 | self._start_time = time.time() | |
f6e6ff79 | 18 | |
c2902b29 MT |
19 | def add_timeout(self, timeout, callback): |
20 | deadline = time.time() + timeout | |
f6e6ff79 | 21 | |
c2902b29 | 22 | return self.application.ioloop.add_timeout(deadline, callback) |
f6e6ff79 | 23 | |
c2902b29 MT |
24 | def on_connection_close(self): |
25 | logging.debug("Connection closed unexpectedly") | |
f6e6ff79 | 26 | |
c2902b29 MT |
27 | def connection_closed(self): |
28 | return self.request.connection.stream.closed() | |
f6e6ff79 | 29 | |
c2902b29 MT |
30 | @property |
31 | def runtime(self): | |
32 | return time.time() - self._start_time | |
f6e6ff79 | 33 | |
f6e6ff79 | 34 | |
c2902b29 MT |
35 | class BaseHandler(LongPollMixin, tornado.web.RequestHandler): |
36 | @property | |
37 | def backend(self): | |
f6e6ff79 | 38 | """ |
4b9167ef | 39 | Shortcut handler to pakfire instance |
f6e6ff79 | 40 | """ |
4b9167ef | 41 | return self.application.backend |
f6e6ff79 | 42 | |
c2902b29 | 43 | def get_basic_auth_credentials(self): |
f6e6ff79 MT |
44 | """ |
45 | This handles HTTP Basic authentication. | |
46 | """ | |
47 | auth_header = self.request.headers.get("Authorization", None) | |
48 | ||
49 | # If no authentication information was provided, we stop here. | |
50 | if not auth_header: | |
c2902b29 | 51 | return None, None |
f6e6ff79 MT |
52 | |
53 | # No basic auth? We cannot handle that. | |
54 | if not auth_header.startswith("Basic "): | |
55 | raise tornado.web.HTTPError(400, "Can only handle Basic auth.") | |
56 | ||
f6e6ff79 | 57 | try: |
c2902b29 MT |
58 | # Decode the authentication information. |
59 | auth_header = base64.decodestring(auth_header[6:]) | |
60 | ||
f6e6ff79 MT |
61 | name, password = auth_header.split(":", 1) |
62 | except: | |
63 | raise tornado.web.HTTPError(400, "Authorization data was malformed") | |
64 | ||
c2902b29 | 65 | return name, password |
f6e6ff79 | 66 | |
c2902b29 MT |
67 | def get_current_user(self): |
68 | name, password = self.get_basic_auth_credentials() | |
69 | if name is None: | |
70 | return | |
f6e6ff79 | 71 | |
c2902b29 MT |
72 | builder = self.backend.builders.auth(name, password) |
73 | if builder: | |
74 | return builder | |
f6e6ff79 | 75 | |
c2902b29 MT |
76 | user = self.backend.users.auth(name, password) |
77 | if user: | |
78 | return user | |
f6e6ff79 MT |
79 | |
80 | @property | |
81 | def builder(self): | |
2c909128 | 82 | if isinstance(self.current_user, builders.Builder): |
c2902b29 | 83 | return self.current_user |
f6e6ff79 | 84 | |
c2902b29 MT |
85 | @property |
86 | def user(self): | |
2c909128 | 87 | if isinstance(self.current_user, users.User): |
c2902b29 | 88 | return self.current_user |
f6e6ff79 | 89 | |
c2902b29 MT |
90 | def get_argument_int(self, *args, **kwargs): |
91 | arg = self.get_argument(*args, **kwargs) | |
f6e6ff79 | 92 | |
c2902b29 MT |
93 | try: |
94 | return int(arg) | |
95 | except (TypeError, ValueError): | |
96 | return None | |
f6e6ff79 | 97 | |
c2902b29 MT |
98 | def get_argument_float(self, *args, **kwargs): |
99 | arg = self.get_argument(*args, **kwargs) | |
f6e6ff79 | 100 | |
c2902b29 MT |
101 | try: |
102 | return float(arg) | |
103 | except (TypeError, ValueError): | |
104 | return None | |
f6e6ff79 | 105 | |
c2902b29 MT |
106 | def get_argument_json(self, *args, **kwargs): |
107 | arg = self.get_argument(*args, **kwargs) | |
f6e6ff79 | 108 | |
c2902b29 MT |
109 | if arg: |
110 | return json.loads(arg) | |
f6e6ff79 | 111 | |
c2902b29 MT |
112 | |
113 | class NoopHandler(BaseHandler): | |
114 | def get(self): | |
115 | if self.builder: | |
116 | self.write("Welcome to the Pakfire hub, %s!" % self.builder.hostname) | |
117 | elif self.user: | |
118 | self.write("Welcome to the Pakfire hub, %s!" % self.user.name) | |
f6e6ff79 | 119 | else: |
c2902b29 | 120 | self.write("Welcome to the Pakfire hub!") |
f6e6ff79 | 121 | |
f6e6ff79 | 122 | |
c2902b29 MT |
123 | class ErrorTestHandler(BaseHandler): |
124 | def get(self, error_code=200): | |
125 | """ | |
126 | For testing a client. | |
f6e6ff79 | 127 | |
c2902b29 MT |
128 | This just returns a HTTP response with the given code. |
129 | """ | |
130 | try: | |
131 | error_code = int(error_code) | |
132 | except ValueError: | |
133 | error_code = 200 | |
f6e6ff79 | 134 | |
c2902b29 | 135 | raise tornado.web.HTTPError(error_code) |
f6e6ff79 | 136 | |
f6e6ff79 | 137 | |
c2902b29 | 138 | # Uploads |
f6e6ff79 | 139 | |
c2902b29 MT |
140 | class UploadsCreateHandler(BaseHandler): |
141 | """ | |
142 | Create a new upload object in the database and return a unique ID | |
143 | to the uploader. | |
144 | """ | |
f6e6ff79 MT |
145 | |
146 | @tornado.web.authenticated | |
c2902b29 MT |
147 | def get(self): |
148 | # XXX Check permissions | |
149 | ||
150 | filename = self.get_argument("filename") | |
151 | filesize = self.get_argument_int("filesize") | |
152 | filehash = self.get_argument("hash") | |
153 | ||
2f64fe68 MT |
154 | with self.db.transaction(): |
155 | upload = self.backend.uploads.create(filename, filesize, | |
156 | filehash, user=self.user, builder=self.builder) | |
f6e6ff79 | 157 | |
2f64fe68 | 158 | self.finish(upload.uuid) |
f6e6ff79 | 159 | |
c2902b29 MT |
160 | |
161 | class UploadsSendChunkHandler(BaseHandler): | |
f6e6ff79 | 162 | @tornado.web.authenticated |
c2902b29 MT |
163 | def post(self, upload_id): |
164 | upload = self.backend.uploads.get_by_uuid(upload_id) | |
f6e6ff79 MT |
165 | if not upload: |
166 | raise tornado.web.HTTPError(404, "Invalid upload id.") | |
167 | ||
168 | if not upload.builder == self.builder: | |
169 | raise tornado.web.HTTPError(403, "Uploading an other host's file.") | |
170 | ||
c2902b29 MT |
171 | chksum = self.get_argument("chksum") |
172 | data = self.get_argument("data") | |
173 | ||
174 | # Decode data. | |
175 | data = base64.b64decode(data) | |
176 | ||
177 | # Calculate hash and compare. | |
178 | h = hashlib.new("sha512") | |
179 | h.update(data) | |
180 | ||
181 | if not chksum == h.hexdigest(): | |
182 | raise tornado.web.HTTPError(400, "Checksum mismatch") | |
183 | ||
184 | # Append the data to file. | |
2f64fe68 MT |
185 | with self.db.transaction(): |
186 | upload.append(data) | |
c2902b29 | 187 | |
f6e6ff79 | 188 | |
c2902b29 | 189 | class UploadsFinishedHandler(BaseHandler): |
f6e6ff79 | 190 | @tornado.web.authenticated |
c2902b29 MT |
191 | def get(self, upload_id): |
192 | upload = self.backend.uploads.get_by_uuid(upload_id) | |
f6e6ff79 MT |
193 | if not upload: |
194 | raise tornado.web.HTTPError(404, "Invalid upload id.") | |
195 | ||
196 | if not upload.builder == self.builder: | |
197 | raise tornado.web.HTTPError(403, "Uploading an other host's file.") | |
198 | ||
199 | # Validate the uploaded data to its hash. | |
200 | ret = upload.validate() | |
201 | ||
202 | # If the validation was successfull, we mark the upload | |
203 | # as finished and send True to the client. | |
204 | if ret: | |
205 | upload.finished() | |
c2902b29 MT |
206 | self.finish("OK") |
207 | ||
208 | return | |
f6e6ff79 MT |
209 | |
210 | # In case the download was corrupted or incomplete, we delete it | |
211 | # and tell the client to start over. | |
2f64fe68 MT |
212 | with self.db.transaction(): |
213 | upload.remove() | |
f6e6ff79 | 214 | |
c2902b29 MT |
215 | self.finish("ERROR: CORRUPTED OR INCOMPLETE FILE") |
216 | ||
217 | ||
218 | class UploadsDestroyHandler(BaseHandler): | |
f6e6ff79 | 219 | @tornado.web.authenticated |
c2902b29 MT |
220 | def get(self, upload_id): |
221 | upload = self.backend.uploads.get_by_uuid(upload_id) | |
f6e6ff79 MT |
222 | if not upload: |
223 | raise tornado.web.HTTPError(404, "Invalid upload id.") | |
224 | ||
225 | if not upload.builder == self.builder: | |
226 | raise tornado.web.HTTPError(403, "Removing an other host's file.") | |
227 | ||
228 | # Remove the upload from the database and trash the data. | |
2f64fe68 MT |
229 | with self.db.transaction(): |
230 | upload.remove() | |
f6e6ff79 MT |
231 | |
232 | ||
c2902b29 | 233 | # Builds |
f6e6ff79 | 234 | |
c2902b29 MT |
235 | class BuildsCreateHandler(BaseHandler): |
236 | @tornado.web.authenticated | |
237 | def get(self): | |
238 | # Get the upload ID of the package file. | |
239 | upload_id = self.get_argument("upload_id") | |
f6e6ff79 | 240 | |
c2902b29 MT |
241 | # Get the identifier of the distribution we build for. |
242 | distro_ident = self.get_argument("distro") | |
f6e6ff79 | 243 | |
c2902b29 MT |
244 | # Get a list of arches to build for. |
245 | arches = self.get_argument("arches", None) | |
246 | if arches == "": | |
247 | arches = None | |
f6e6ff79 | 248 | |
c2902b29 MT |
249 | # Process build type. |
250 | build_type = self.get_argument("build_type") | |
251 | if build_type == "release": | |
252 | check_for_duplicates = True | |
253 | elif build_type == "scratch": | |
254 | check_for_duplicates = False | |
255 | else: | |
256 | raise tornado.web.HTTPError(400, "Invalid build type") | |
f6e6ff79 | 257 | |
c2902b29 MT |
258 | ## Check if the user has permission to create a build. |
259 | # Users only have the permission to create scratch builds. | |
260 | if self.user and not build_type == "scratch": | |
261 | raise tornado.web.HTTPError(403, "Users are only allowed to upload scratch builds") | |
f6e6ff79 | 262 | |
c2902b29 MT |
263 | # Get previously uploaded file to create this build from. |
264 | upload = self.backend.uploads.get_by_uuid(upload_id) | |
265 | if not upload: | |
266 | raise tornado.web.HTTPError(400, "Upload does not exist: %s" % upload_id) | |
f6e6ff79 | 267 | |
c2902b29 MT |
268 | # Check if the uploaded file belongs to this user/builder. |
269 | if self.user and not upload.user == self.user: | |
270 | raise tornado.web.HTTPError(400, "Upload does not belong to this user.") | |
f6e6ff79 | 271 | |
c2902b29 MT |
272 | elif self.builder and not upload.builder == self.builder: |
273 | raise tornado.web.HTTPError(400, "Upload does not belong to this builder.") | |
f6e6ff79 | 274 | |
c2902b29 MT |
275 | # Get distribution this package should be built for. |
276 | distro = self.backend.distros.get_by_ident(distro_ident) | |
277 | if not distro: | |
278 | distro = self.backend.distros.get_default() | |
f6e6ff79 | 279 | |
c2902b29 MT |
280 | # Open the package that was uploaded earlier and add it to |
281 | # the database. Create a new build object from the uploaded package. | |
282 | args = { | |
283 | "arches" : arches, | |
284 | "check_for_duplicates" : check_for_duplicates, | |
285 | "distro" : distro, | |
286 | "type" : build_type, | |
287 | } | |
288 | if self.user: | |
289 | args["owner"] = self.user | |
f6e6ff79 | 290 | |
c2902b29 | 291 | try: |
2c909128 | 292 | pkg, build = builds.import_from_package(self.backend, upload.path, **args) |
f6e6ff79 | 293 | |
c2902b29 MT |
294 | except: |
295 | # Raise any exception. | |
296 | raise | |
f6e6ff79 | 297 | |
c2902b29 MT |
298 | else: |
299 | # Creating the build will move the file to the build directory, | |
300 | # so we can safely remove the uploaded file. | |
301 | upload.remove() | |
f6e6ff79 | 302 | |
c2902b29 MT |
303 | # Send the build ID back to the user. |
304 | self.finish(build.uuid) | |
f6e6ff79 | 305 | |
f6e6ff79 | 306 | |
c2902b29 MT |
307 | class BuildsGetHandler(BaseHandler): |
308 | def get(self, build_uuid): | |
309 | build = self.backend.builds.get_by_uuid(build_uuid) | |
310 | if not build: | |
311 | raise tornado.web.HTTPError(404, "Could not find build: %s" % build_uuid) | |
f6e6ff79 | 312 | |
c2902b29 MT |
313 | ret = { |
314 | "distro" : build.distro.identifier, | |
315 | "jobs" : [j.uuid for j in build.jobs], | |
316 | "name" : build.name, | |
317 | "package" : build.pkg.uuid, | |
318 | "priority" : build.priority, | |
d31d17af | 319 | "score" : build.score, |
c2902b29 MT |
320 | "severity" : build.severity, |
321 | "state" : build.state, | |
322 | "sup_arches" : build.supported_arches, | |
323 | "time_created" : build.created.isoformat(), | |
324 | "type" : build.type, | |
325 | "uuid" : build.uuid, | |
f6e6ff79 MT |
326 | } |
327 | ||
328 | # If the build is in a repository, update that bit. | |
329 | if build.repo: | |
c2902b29 | 330 | ret["repo"] = build.repo.identifier |
f6e6ff79 | 331 | |
c2902b29 | 332 | self.finish(ret) |
f6e6ff79 | 333 | |
f6e6ff79 | 334 | |
c2902b29 | 335 | # Jobs |
f6e6ff79 | 336 | |
ba9f9092 MT |
337 | class JobsBaseHandler(BaseHandler): |
338 | def job2json(self, job): | |
f6e6ff79 | 339 | ret = { |
22b715d7 | 340 | "arch" : job.arch, |
c2902b29 | 341 | "build" : job.build.uuid, |
c2902b29 MT |
342 | "duration" : job.duration, |
343 | "name" : job.name, | |
344 | "packages" : [p.uuid for p in job.packages], | |
345 | "state" : job.state, | |
346 | "time_created" : job.time_created.isoformat(), | |
4f90cf84 | 347 | "type" : "test" if job.test else "release", |
c2902b29 | 348 | "uuid" : job.uuid, |
f6e6ff79 MT |
349 | } |
350 | ||
ba9f9092 MT |
351 | if job.builder: |
352 | ret["builder"] = job.builder.hostname | |
353 | ||
c2902b29 MT |
354 | if job.time_started: |
355 | ret["time_started"] = job.time_started.isoformat() | |
f6e6ff79 | 356 | |
c2902b29 MT |
357 | if job.time_finished: |
358 | ret["time_finished"] = job.time_finished.isoformat() | |
f6e6ff79 | 359 | |
ba9f9092 MT |
360 | return ret |
361 | ||
362 | ||
363 | class JobsGetActiveHandler(JobsBaseHandler): | |
364 | def get(self): | |
365 | # Get list of all active jobs. | |
366 | jobs = self.backend.jobs.get_active() | |
367 | ||
368 | args = { | |
369 | "jobs" : [self.job2json(j) for j in jobs], | |
370 | } | |
371 | ||
372 | self.finish(args) | |
373 | ||
374 | ||
375 | class JobsGetLatestHandler(JobsBaseHandler): | |
376 | def get(self): | |
377 | limit = self.get_argument_int("limit", 5) | |
378 | ||
379 | # Get the latest jobs. | |
380 | jobs = self.backend.jobs.get_latest(age="24 HOUR", limit=limit) | |
381 | ||
382 | args = { | |
383 | "jobs" : [self.job2json(j) for j in jobs], | |
384 | } | |
385 | ||
386 | self.finish(args) | |
387 | ||
388 | ||
389 | class JobsGetQueueHandler(JobsBaseHandler): | |
390 | def get(self): | |
391 | limit = self.get_argument_int("limit", 5) | |
392 | ||
393 | # Get the job queue. | |
fd43d5e1 MT |
394 | jobs = [] |
395 | for job in self.backend.jobqueue: | |
396 | jobs.append(job) | |
397 | ||
398 | limit -= 1 | |
399 | if not limit: break | |
ba9f9092 MT |
400 | |
401 | args = { | |
402 | "jobs" : [self.job2json(j) for j in jobs], | |
403 | } | |
404 | ||
405 | self.finish(args) | |
406 | ||
407 | ||
408 | class JobsGetHandler(JobsBaseHandler): | |
409 | def get(self, job_uuid): | |
410 | job = self.backend.jobs.get_by_uuid(job_uuid) | |
411 | if not job: | |
412 | raise tornado.web.HTTPError(404, "Could not find job: %s" % job_uuid) | |
413 | ||
ba9f9092 | 414 | ret = self.job2json(job) |
c2902b29 | 415 | self.finish(ret) |
f6e6ff79 | 416 | |
f6e6ff79 | 417 | |
c2902b29 | 418 | # Packages |
f6e6ff79 | 419 | |
c2902b29 MT |
420 | class PackagesGetHandler(BaseHandler): |
421 | def get(self, package_uuid): | |
422 | pkg = self.backend.packages.get_by_uuid(package_uuid) | |
f6e6ff79 | 423 | if not pkg: |
c2902b29 | 424 | raise tornado.web.HTTPError(404, "Could not find package: %s" % package_uuid) |
f6e6ff79 MT |
425 | |
426 | ret = { | |
22b715d7 | 427 | "arch" : pkg.arch, |
c2902b29 MT |
428 | "build_id" : pkg.build_id, |
429 | "build_host" : pkg.build_host, | |
430 | "build_time" : pkg.build_time.isoformat(), | |
431 | "description" : pkg.description, | |
432 | "epoch" : pkg.epoch, | |
433 | "filesize" : pkg.filesize, | |
f6e6ff79 MT |
434 | "friendly_name" : pkg.friendly_name, |
435 | "friendly_version" : pkg.friendly_version, | |
436 | "groups" : pkg.groups, | |
c2902b29 | 437 | "hash_sha512" : pkg.hash_sha512, |
f6e6ff79 | 438 | "license" : pkg.license, |
c2902b29 MT |
439 | "name" : pkg.name, |
440 | "release" : pkg.release, | |
f6e6ff79 | 441 | "size" : pkg.size, |
c2902b29 MT |
442 | "summary" : pkg.summary, |
443 | "type" : pkg.type, | |
444 | "url" : pkg.url, | |
445 | "uuid" : pkg.uuid, | |
446 | "version" : pkg.version, | |
f6e6ff79 MT |
447 | |
448 | # Dependencies. | |
449 | "prerequires" : pkg.prerequires, | |
450 | "requires" : pkg.requires, | |
451 | "provides" : pkg.provides, | |
452 | "obsoletes" : pkg.obsoletes, | |
453 | "conflicts" : pkg.conflicts, | |
f6e6ff79 MT |
454 | } |
455 | ||
c2902b29 MT |
456 | if pkg.type == "source": |
457 | ret["supported_arches"] = pkg.supported_arches | |
458 | ||
2c909128 | 459 | if isinstance(pkg.maintainer, users.User): |
f6e6ff79 MT |
460 | ret["maintainer"] = "%s <%s>" % (pkg.maintainer.realname, pkg.maintainer.email) |
461 | elif pkg.maintainer: | |
462 | ret["maintainer"] = pkg.maintainer | |
463 | ||
464 | if pkg.distro: | |
c2902b29 | 465 | ret["distro"] = pkg.distro.identifier |
f6e6ff79 | 466 | |
c2902b29 | 467 | self.finish(ret) |
f6e6ff79 MT |
468 | |
469 | ||
c2902b29 | 470 | # Builders |
f6e6ff79 | 471 | |
c2902b29 MT |
472 | class BuildersBaseHandler(BaseHandler): |
473 | def prepare(self): | |
474 | # The request must come from an authenticated buider. | |
475 | if not self.builder: | |
476 | raise tornado.web.HTTPError(403) | |
f6e6ff79 | 477 | |
f6e6ff79 | 478 | |
c2902b29 | 479 | class BuildersInfoHandler(BuildersBaseHandler): |
f6e6ff79 | 480 | @tornado.web.authenticated |
c2902b29 MT |
481 | def post(self): |
482 | args = { | |
483 | # CPU info | |
484 | "cpu_model" : self.get_argument("cpu_model", None), | |
485 | "cpu_count" : self.get_argument("cpu_count", None), | |
486 | "cpu_arch" : self.get_argument("cpu_arch", None), | |
487 | "cpu_bogomips" : self.get_argument("cpu_bogomips", None), | |
488 | ||
489 | # Pakfire | |
490 | "pakfire_version" : self.get_argument("pakfire_version", None), | |
491 | "host_key" : self.get_argument("host_key", None), | |
492 | ||
493 | # OS | |
494 | "os_name" : self.get_argument("os_name", None), | |
495 | } | |
496 | self.builder.update_info(**args) | |
f6e6ff79 | 497 | |
f6e6ff79 | 498 | |
c2902b29 MT |
499 | class BuildersKeepaliveHandler(BuildersBaseHandler): |
500 | @tornado.web.authenticated | |
501 | def post(self): | |
502 | args = { | |
503 | # Load average | |
504 | "loadavg1" : self.get_argument_float("loadavg1", None), | |
505 | "loadavg5" : self.get_argument_float("loadavg5", None), | |
506 | "loadavg15" : self.get_argument_float("loadavg15", None), | |
507 | ||
508 | # Memory | |
509 | "mem_total" : self.get_argument_int("mem_total", None), | |
510 | "mem_free" : self.get_argument_int("mem_free", None), | |
511 | ||
512 | # swap | |
513 | "swap_total" : self.get_argument_int("swap_total", None), | |
514 | "swap_free" : self.get_argument_int("swap_free", None), | |
515 | ||
516 | # Disk space | |
517 | "space_free" : self.get_argument_int("space_free", None), | |
518 | } | |
519 | self.builder.update_keepalive(**args) | |
f6e6ff79 | 520 | |
c2902b29 | 521 | self.finish("OK") |
f6e6ff79 | 522 | |
f6e6ff79 | 523 | |
c2902b29 MT |
524 | class BuildersJobsQueueHandler(BuildersBaseHandler): |
525 | @tornado.web.asynchronous | |
526 | @tornado.web.authenticated | |
527 | def get(self): | |
528 | self.callback() | |
f6e6ff79 | 529 | |
c2902b29 MT |
530 | def callback(self): |
531 | # Break if the connection has been closed in the mean time. | |
532 | if self.connection_closed(): | |
533 | logging.warning("Connection closed") | |
f6e6ff79 MT |
534 | return |
535 | ||
c2902b29 MT |
536 | # Check if there is a job for us. |
537 | job = self.builder.get_next_job() | |
538 | ||
539 | # Got no job, wait and try again. | |
f6e6ff79 | 540 | if not job: |
c2902b29 MT |
541 | # Check if we have been running for too long. |
542 | if self.runtime >= self.max_runtime: | |
543 | logging.debug("Exceeded max. runtime. Finishing request.") | |
544 | return self.finish() | |
f6e6ff79 | 545 | |
c2902b29 MT |
546 | # Try again in a jiffy. |
547 | self.add_timeout(self.heartbeat, self.callback) | |
548 | return | |
f6e6ff79 MT |
549 | |
550 | try: | |
551 | # Set job to dispatching state. | |
552 | job.state = "dispatching" | |
553 | ||
554 | # Set our build host. | |
555 | job.builder = self.builder | |
556 | ||
557 | ret = { | |
558 | "id" : job.uuid, | |
22b715d7 | 559 | "arch" : job.arch, |
c2902b29 MT |
560 | "source_url" : job.build.source_download, |
561 | "source_hash_sha512" : job.build.source_hash_sha512, | |
4f90cf84 | 562 | "type" : "test" if job.test else "release", |
f6e6ff79 MT |
563 | "config" : job.get_config(), |
564 | } | |
565 | ||
566 | # Send build information to the builder. | |
c2902b29 | 567 | self.finish(ret) |
f6e6ff79 MT |
568 | except: |
569 | # If anything went wrong, we reset the state. | |
570 | job.state = "pending" | |
571 | raise | |
572 | ||
c2902b29 MT |
573 | @property |
574 | def heartbeat(self): | |
575 | return 15 # 15 seconds | |
576 | ||
577 | @property | |
578 | def max_runtime(self): | |
579 | timeout = self.get_argument_int("timeout", None) | |
580 | if timeout: | |
581 | return timeout - self.heartbeat | |
582 | ||
583 | return 300 # 5 min | |
584 | ||
585 | ||
586 | class BuildersJobsStateHandler(BuildersBaseHandler): | |
587 | @tornado.web.authenticated | |
588 | def post(self, job_uuid, state): | |
589 | job = self.backend.jobs.get_by_uuid(job_uuid) | |
f6e6ff79 MT |
590 | if not job: |
591 | raise tornado.web.HTTPError(404, "Invalid job id.") | |
592 | ||
593 | if not job.builder == self.builder: | |
594 | raise tornado.web.HTTPError(403, "Altering another builder's build.") | |
595 | ||
596 | # Save information to database. | |
597 | job.state = state | |
c2902b29 MT |
598 | |
599 | message = self.get_argument("message", None) | |
f6e6ff79 MT |
600 | job.update_message(message) |
601 | ||
c2902b29 MT |
602 | self.finish("OK") |
603 | ||
604 | ||
605 | class BuildersJobsBuildrootHandler(BuildersBaseHandler): | |
606 | @tornado.web.authenticated | |
607 | def post(self, job_uuid): | |
608 | job = self.backend.jobs.get_by_uuid(job_uuid) | |
609 | if not job: | |
610 | raise tornado.web.HTTPError(404, "Invalid job id.") | |
611 | ||
612 | if not job.builder == self.builder: | |
613 | raise tornado.web.HTTPError(403, "Altering another builder's build.") | |
f6e6ff79 | 614 | |
c2902b29 MT |
615 | # Get buildroot. |
616 | buildroot = self.get_argument_json("buildroot", None) | |
617 | if buildroot: | |
618 | job.save_buildroot(buildroot) | |
619 | ||
620 | self.finish("OK") | |
621 | ||
622 | ||
623 | class BuildersJobsAddFileHandler(BuildersBaseHandler): | |
624 | @tornado.web.authenticated | |
625 | def post(self, job_uuid, upload_id): | |
626 | type = self.get_argument("type") | |
f6e6ff79 MT |
627 | assert type in ("package", "log") |
628 | ||
629 | # Fetch job we are working on and check if it is actually ours. | |
c2902b29 | 630 | job = self.backend.jobs.get_by_uuid(job_uuid) |
f6e6ff79 MT |
631 | if not job: |
632 | raise tornado.web.HTTPError(404, "Invalid job id.") | |
633 | ||
634 | if not job.builder == self.builder: | |
635 | raise tornado.web.HTTPError(403, "Altering another builder's job.") | |
636 | ||
637 | # Fetch uploaded file object and check we uploaded it ourself. | |
c2902b29 | 638 | upload = self.backend.uploads.get_by_uuid(upload_id) |
f6e6ff79 MT |
639 | if not upload: |
640 | raise tornado.web.HTTPError(404, "Invalid upload id.") | |
641 | ||
642 | if not upload.builder == self.builder: | |
643 | raise tornado.web.HTTPError(403, "Using an other host's file.") | |
644 | ||
645 | # Remove all files that have to be deleted, first. | |
c2902b29 | 646 | self.backend.cleanup_files() |
f6e6ff79 MT |
647 | |
648 | try: | |
649 | job.add_file(upload.path) | |
650 | ||
651 | finally: | |
652 | # Finally, remove the uploaded file. | |
653 | upload.remove() | |
654 | ||
c2902b29 | 655 | self.finish("OK") |