]> git.ipfire.org Git - people/jschlag/pbs.git/blame - src/buildservice/builders.py
Merge branch 'master' of git://git.ipfire.org/pbs
[people/jschlag/pbs.git] / src / buildservice / builders.py
CommitLineData
9137135a
MT
1#!/usr/bin/python
2
2c909128 3from __future__ import absolute_import, division
f96eb5ed 4
9137135a
MT
5import datetime
6import hashlib
7import logging
8import random
9import string
10import time
11
2c909128
MT
12from . import base
13from . import logs
f6e6ff79 14
3e990438
MT
15from .decorators import *
16
2c909128 17from .users import generate_password_hash, check_password_hash, generate_random_string
9137135a
MT
18
19class Builders(base.Object):
e704b8e2
MT
20 def _get_builder(self, query, *args):
21 res = self.db.get(query, *args)
22
23 if res:
24 return Builder(self.backend, res.id, data=res)
25
26 def _get_builders(self, query, *args):
27 res = self.db.query(query, *args)
28
29 for row in res:
30 yield Builder(self.backend, row.id, data=row)
31
32 def __iter__(self):
33 builders = self._get_builders("SELECT * FROM builders \
34 WHERE deleted IS FALSE ORDER BY name")
35
36 return iter(builders)
37
38 def create(self, name, user=None, log=True):
39 """
40 Creates a new builder.
41 """
42 builder = self._get_builder("INSERT INTO builders(name) \
43 VALUES(%s) RETURNING *", name)
44
45 # Generate a new passphrase.
46 passphrase = builder.regenerate_passphrase()
47
48 # Log what we have done.
49 if log:
50 builder.log("created", user=user)
51
52 # The Builder object and the passphrase are returned.
53 return builder, passphrase
54
f6e6ff79
MT
55 def auth(self, name, passphrase):
56 # If either name or passphrase is None, we don't check at all.
57 if None in (name, passphrase):
58 return
59
60 # Search for the hostname in the database.
e704b8e2
MT
61 builder = self._get_builder("SELECT * FROM builders \
62 WHERE name = %s AND deleted IS FALSE", name)
f6e6ff79
MT
63
64 # If the builder was not found or the passphrase does not match,
65 # you have bad luck.
66 if not builder or not builder.validate_passphrase(passphrase):
67 return
68
69 # Otherwise we return the Builder object.
70 return builder
71
e704b8e2
MT
72 def get_by_id(self, builder_id):
73 return self._get_builder("SELECT * FROM builders WHERE id = %s", builder_id)
9137135a
MT
74
75 def get_by_name(self, name):
e704b8e2
MT
76 return self._get_builder("SELECT * FROM builders \
77 WHERE name = %s AND deleted IS FALSE", name)
9137135a 78
f6e6ff79
MT
79 def get_history(self, limit=None, offset=None, builder=None, user=None):
80 query = "SELECT * FROM builders_history"
81 args = []
82
83 conditions = []
84
85 if builder:
86 conditions.append("builder_id = %s")
87 args.append(builder.id)
88
89 if user:
90 conditions.append("user_id = %s")
91 args.append(user.id)
92
93 if conditions:
94 query += " WHERE %s" % " AND ".join(conditions)
95
96 query += " ORDER BY time DESC"
97
98 if limit:
99 if offset:
100 query += " LIMIT %s,%s"
101 args += [offset, limit,]
102 else:
103 query += " LIMIT %s"
104 args += [limit,]
105
106 entries = []
107 for entry in self.db.query(query, *args):
108 entry = logs.BuilderLogEntry(self.pakfire, entry)
109 entries.append(entry)
110
111 return entries
112
9137135a 113
3e990438 114class Builder(base.DataObject):
92431da4
MT
115 table = "builders"
116
e704b8e2
MT
117 def __eq__(self, other):
118 if isinstance(other, self.__class__):
119 return self.id == other.id
9137135a 120
e704b8e2
MT
121 def __lt__(self, other):
122 if isinstance(other, self.__class__):
123 return self.name < other.name
f6e6ff79
MT
124
125 def log(self, action, user=None):
126 user_id = None
127 if user:
128 user_id = user.id
129
130 self.db.execute("INSERT INTO builders_history(builder_id, action, user_id, time) \
131 VALUES(%s, %s, %s, NOW())", self.id, action, user_id)
9137135a 132
9137135a 133 def regenerate_passphrase(self):
f6e6ff79
MT
134 """
135 Generates a new random passphrase and stores it as a salted hash
136 to the database.
137
138 The new passphrase is returned to be sent to the user (once).
139 """
e704b8e2
MT
140 # Generate a random string with 40 chars.
141 passphrase = generate_random_string(length=40)
f6e6ff79
MT
142
143 # Create salted hash.
144 passphrase_hash = generate_password_hash(passphrase)
145
146 # Store the hash in the database.
3e990438 147 self._set_attribute("passphrase", passphrase_hash)
9137135a 148
f6e6ff79
MT
149 # Return the clear-text passphrase.
150 return passphrase
9137135a
MT
151
152 def validate_passphrase(self, passphrase):
f6e6ff79
MT
153 """
154 Compare the given passphrase with the one stored in the database.
155 """
156 return check_password_hash(passphrase, self.data.passphrase)
157
3e990438
MT
158 # Description
159
160 def set_description(self, description):
161 self._set_attribute("description", description)
162
163 description = property(lambda s: s.data.description or "", set_description)
f6e6ff79 164
f6e6ff79
MT
165 @property
166 def keepalive(self):
167 """
168 Returns time of last keepalive message from this host.
169 """
170 return self.data.time_keepalive
171
c2902b29
MT
172 def update_keepalive(self, loadavg1=None, loadavg5=None, loadavg15=None,
173 mem_total=None, mem_free=None, swap_total=None, swap_free=None,
174 space_free=None):
f6e6ff79
MT
175 """
176 Update the keepalive timestamp of this machine.
177 """
c2902b29
MT
178 self.db.execute("UPDATE builders SET time_keepalive = NOW(), \
179 loadavg1 = %s, loadavg5 = %s, loadavg15 = %s, space_free = %s, \
180 mem_total = %s, mem_free = %s, swap_total = %s, swap_free = %s \
181 WHERE id = %s", loadavg1, loadavg5, loadavg15, space_free,
182 mem_total, mem_free, swap_total, swap_free, self.id)
183
184 def update_info(self, cpu_model=None, cpu_count=None, cpu_arch=None, cpu_bogomips=None,
185 pakfire_version=None, host_key=None, os_name=None):
f6e6ff79
MT
186 # Update all the rest.
187 self.db.execute("UPDATE builders SET time_updated = NOW(), \
c2902b29
MT
188 pakfire_version = %s, cpu_model = %s, cpu_count = %s, cpu_arch = %s, \
189 cpu_bogomips = %s, host_key_id = %s, os_name = %s WHERE id = %s",
190 pakfire_version, cpu_model, cpu_count, cpu_arch, cpu_bogomips,
191 host_key, os_name, self.id)
f6e6ff79 192
e704b8e2
MT
193 def set_enabled(self, enabled):
194 self._set_attribute("enabled", enabled)
9137135a 195
e704b8e2 196 enabled = property(lambda s: s.data.enabled, set_enabled)
9137135a
MT
197
198 @property
199 def disabled(self):
f6e6ff79
MT
200 return not self.enabled
201
e704b8e2
MT
202 @property
203 def native_arch(self):
204 """
205 The native architecture of this builder
206 """
207 return self.cpu_arch
f6e6ff79 208
e704b8e2
MT
209 @lazy_property
210 def supported_arches(self):
211 # Every builder supports noarch
212 arches = ["noarch"]
f6e6ff79 213
e704b8e2
MT
214 # We can always build our native architeture
215 if self.native_arch:
216 arches.append(self.native_arch)
f6e6ff79 217
e704b8e2 218 # Get all compatible architectures
3e990438 219 res = self.db.query("SELECT build_arch FROM arches_compat \
e704b8e2 220 WHERE native_arch = %s", self.native_arch)
f6e6ff79 221
e704b8e2
MT
222 for row in res:
223 if not row.build_arch in arches:
224 arches.append(row.build_arch)
f6e6ff79 225
e704b8e2 226 return sorted(arches)
9137135a 227
fd43d5e1
MT
228 def set_testmode(self, testmode):
229 self._set_attribute("testmode", testmode)
f6e6ff79 230
fd43d5e1 231 testmode = property(lambda s: s.data.testmode, set_testmode)
f6e6ff79 232
9137135a 233 def set_max_jobs(self, value):
3e990438 234 self._set_attribute("max_jobs", value)
9137135a 235
3e990438 236 max_jobs = property(lambda s: s.data.max_jobs, set_max_jobs)
9137135a
MT
237
238 @property
239 def name(self):
240 return self.data.name
241
242 @property
243 def hostname(self):
244 return self.name
245
246 @property
247 def passphrase(self):
248 return self.data.passphrase
249
c2902b29
MT
250 # Load average
251
9137135a
MT
252 @property
253 def loadavg(self):
c2902b29 254 return ", ".join(["%.2f" % l for l in (self.loadavg1, self.loadavg5, self.loadavg15)])
9137135a 255
f6e6ff79 256 @property
c2902b29
MT
257 def loadavg1(self):
258 return self.data.loadavg1 or 0.0
259
260 @property
261 def loadavg5(self):
262 return self.data.loadavg5 or 0.0
f6e6ff79 263
c2902b29
MT
264 @property
265 def loadavg15(self):
266 return self.data.loadavg15 or 0.0
f6e6ff79
MT
267
268 @property
269 def pakfire_version(self):
270 return self.data.pakfire_version or ""
9137135a 271
c2902b29
MT
272 @property
273 def os_name(self):
274 return self.data.os_name or ""
275
9137135a
MT
276 @property
277 def cpu_model(self):
f6e6ff79
MT
278 return self.data.cpu_model or ""
279
280 @property
281 def cpu_count(self):
282 return self.data.cpu_count
9137135a
MT
283
284 @property
c2902b29
MT
285 def cpu_arch(self):
286 return self.data.cpu_arch
287
288 @property
289 def cpu_bogomips(self):
290 return self.data.cpu_bogomips or 0.0
291
292 @property
293 def mem_percentage(self):
294 if not self.mem_total:
295 return None
296
297 return self.mem_used * 100 / self.mem_total
9137135a
MT
298
299 @property
c2902b29
MT
300 def mem_total(self):
301 return self.data.mem_total
302
303 @property
304 def mem_used(self):
305 if self.mem_total and self.mem_free:
306 return self.mem_total - self.mem_free
307
308 @property
309 def mem_free(self):
310 return self.data.mem_free
311
312 @property
313 def swap_percentage(self):
314 if not self.swap_total:
315 return None
316
317 return self.swap_used * 100 / self.swap_total
318
319 @property
320 def swap_total(self):
321 return self.data.swap_total
322
323 @property
324 def swap_used(self):
325 if self.swap_total and self.swap_free:
326 return self.swap_total - self.swap_free
327
328 @property
329 def swap_free(self):
330 return self.data.swap_free
331
332 @property
333 def space_free(self):
334 return self.data.space_free
f6e6ff79 335
f6e6ff79
MT
336 @property
337 def host_key_id(self):
338 return self.data.host_key_id
339
340 @property
341 def state(self):
9137135a 342 if self.disabled:
f6e6ff79 343 return "disabled"
9137135a 344
f6e6ff79
MT
345 if self.data.time_keepalive is None:
346 return "offline"
9137135a 347
f96eb5ed
MT
348 #if self.data.updated >= 5*60:
349 # return "offline"
9137135a 350
f6e6ff79 351 return "online"
9137135a 352
3e990438
MT
353 @lazy_property
354 def active_jobs(self, *args, **kwargs):
355 return self.pakfire.jobs.get_active(builder=self, *args, **kwargs)
163d9d8b
MT
356
357 @property
358 def too_many_jobs(self):
359 """
360 Tell if this host is already running enough or too many jobs.
361 """
3e990438 362 return len(self.active_jobs) >= self.max_jobs
9e8a20d7 363
fd43d5e1
MT
364 @lazy_property
365 def jobqueue(self):
366 return self.backend.jobqueue.for_arches(self.supported_arches)
9e8a20d7 367
c2902b29
MT
368 def get_next_job(self):
369 """
370 Returns the next job in line for this builder.
371 """
484533b6
MT
372 # Don't send any jobs to disabled builders
373 if not self.enabled:
374 return
375
fd43d5e1
MT
376 # Don't return anything if the builder has already too many jobs running
377 if self.too_many_jobs:
378 return
379
380 for job in self.jobqueue:
381 # Only allow building test jobs in test mode
4f90cf84 382 if self.testmode and not job.test:
fd43d5e1 383 continue
9137135a 384
fd43d5e1 385 return job
f6e6ff79
MT
386
387 def get_history(self, *args, **kwargs):
388 kwargs["builder"] = self
389
390 return self.pakfire.builders.get_history(*args, **kwargs)