]> git.ipfire.org Git - pbs.git/blob - src/web/jobs.py
Merge Pakfire Hub into the main webapp
[pbs.git] / src / web / jobs.py
1 #!/usr/bin/python3
2
3 import logging
4 import tornado.web
5 import tornado.websocket
6
7 from . import base
8 from . import ui_modules
9
10 # Setup logging
11 log = logging.getLogger("pakfire.buildservice.web.jobs")
12
13 class APIv1QueueHandler(base.APIMixin, tornado.websocket.WebSocketHandler):
14 """
15 Builders connect to this handler which will add them to a list of connections.
16
17 For all connections, we regularly check if we have any new build jobs, and if so,
18 we will send them the job.
19 """
20
21 # Don't allow users to authenticate
22 allow_users = False
23
24 @property
25 def builder(self):
26 return self.current_user
27
28 @tornado.web.authenticated
29 async def open(self):
30 # Register a new connection
31 await self.backend.jobqueue.open(builder=self.builder, connection=self)
32
33 def on_close(self):
34 # Close the connection
35 self.backend.jobqueue.close(builder=self.builder, connection=self)
36
37 def assign_job(self, job):
38 log.debug("Sending job %s to %s" % (job, self))
39
40 # Assign this job
41 with self.db.transaction():
42 job.assign(builder=self.builder)
43
44 self.write_message({
45 "message" : "job",
46
47 # Add job information
48 "id" : job.uuid,
49 "name" : "%s" % job,
50 "arch" : job.arch,
51
52 # Is this a test job?
53 "test" : job.test,
54
55 # Send the pakfire configuration without using any mirrors
56 "conf" : "%s" % job.pakfire(mirrored=False),
57
58 # URL to the package
59 "pkg" : job.pkg.download_url,
60 })
61
62
63 class APIv1DetailHandler(base.APIMixin, tornado.websocket.WebSocketHandler):
64 """
65 Builders connect to this handler when they are running a build.
66
67 We can pass information about this build around in real time.
68 """
69 # Don't allow users to authenticate
70 allow_users = False
71
72 @tornado.web.authenticated
73 def open(self, job_id):
74 self.job = self.backend.jobs.get_by_uuid(job_id)
75 if not self.job:
76 raise tornado.web.HTTPError(404, "Could not find job %s" % job_id)
77
78 # Check if the builder matches
79 if not self.current_user == self.job.builder:
80 raise tornado.web.HTTPError(403, "Job %s belongs to %s, not %s" % \
81 (self.job, self.job.builder, self.current_user))
82
83 log.debug("Connection opened for %s by %s" % (self.job, self.current_user))
84
85 async def on_message(self, message):
86 message = self._decode_json_message(message)
87
88 # Get message type
89 t = message.get("message")
90
91 # Handle status messages
92 if t == "status":
93 pass
94
95 # Handle log messages
96 elif t == "log":
97 pass
98
99 # Handle finished message
100 elif t == "finished":
101 await self._handle_finished(**message)
102
103 # Unknown message
104 else:
105 log.warning("Received a message of an unknown type: %s" % t)
106
107 async def _handle_finished(self, success=False, logfile=None, packages=[], **kwargs):
108 """
109 Called when a job has finished - whether successfully or not
110 """
111 # Fetch the log
112 if logfile:
113 logfile = self.backend.uploads.get_by_uuid(logfile)
114
115 # Fetch the packages
116 if packages:
117 packages = [self.backend.uploads.get_by_uuid(p) for p in packages]
118
119 # Mark the job as finished
120 with self.db.transaction():
121 await self.job.finished(success=success, logfile=logfile, packages=packages)
122
123 # Try to dispatch the next job
124 await self.backend.jobqueue.dispatch_jobs()
125
126
127 class QueueHandler(base.BaseHandler):
128 def get(self):
129 self.render("queue.html", queue=self.backend.jobqueue)
130
131
132 class LogHandler(base.BaseHandler):
133 async def get(self, uuid):
134 job = self.backend.jobs.get_by_uuid(uuid)
135 if not job:
136 raise tornado.web.HTTPError("Could not find job %s" % uuid)
137
138 tail = self.get_argument_int("tail", None)
139
140 # Should we tail the log, or stream the entire file?
141 try:
142 log = await job.tail_log(tail) if tail else await job.open_log()
143
144 # Send 404 if there is no log file
145 except FileNotFoundError as e:
146 raise tornado.web.HTTPError(404, "Could not find log for %s" % job) from e
147
148 # Set Content-Type header
149 self.set_header("Content-Type", "text/plain")
150
151 # Stream the entire log
152 for line in log:
153 self.write(line)
154
155
156 class JobBuildrootHandler(base.BaseHandler):
157 def get(self, uuid):
158 job = self.backend.jobs.get_by_uuid(uuid)
159 if not job:
160 raise tornado.web.HTTPError(404, "Job not found: %s" % uuid)
161
162 # Calculate the download size and buildroot size.
163 download_size = 0
164 buildroot_size = 0
165
166 for name, uuid, pkg in job.buildroot:
167 if not pkg:
168 continue
169
170 download_size += pkg.filesize
171 buildroot_size += pkg.size
172
173 self.render("jobs-buildroot.html", job=job, build=job.build,
174 buildroot=job.buildroot, download_size=download_size,
175 buildroot_size=buildroot_size)
176
177
178 class AbortHandler(base.BaseHandler):
179 @tornado.web.authenticated
180 def get(self, uuid):
181 job = self.backend.jobs.get_by_uuid(uuid)
182 if not job:
183 raise tornado.web.HTTPError(404, "Job not found: %s" % uuid)
184
185 # Check for permissions
186 if not job.has_perm(self.current_user):
187 raise tornado.web.HTTPError(403)
188
189 self.render("jobs/abort.html", job=job)
190
191 @tornado.web.authenticated
192 async def post(self, uuid):
193 job = self.backend.jobs.get_by_uuid(uuid)
194 if not job:
195 raise tornado.web.HTTPError(404, "Job not found: %s" % uuid)
196
197 # Check for permissions
198 if not job.has_perm(self.current_user):
199 raise tornado.web.HTTPError(403)
200
201 with self.db.transaction():
202 await job.abort(self.current_user)
203
204 self.redirect("/builds/%s" % job.build.uuid)
205
206
207 class ListModule(ui_modules.UIModule):
208 def render(self, jobs, show_arch_only=False, show_packages=False):
209 return self.render_string("jobs/modules/list.html", jobs=jobs,
210 show_arch_only=show_arch_only, show_packages=show_packages)