]>
git.ipfire.org Git - people/jschlag/pbs.git/blob - src/buildservice/builders.py
3 from __future__
import absolute_import
, division
15 from .decorators
import *
17 from .users
import generate_password_hash
, check_password_hash
, generate_random_string
19 class Builders(base
.Object
):
20 def _get_builder(self
, query
, *args
):
21 res
= self
.db
.get(query
, *args
)
24 return Builder(self
.backend
, res
.id, data
=res
)
26 def _get_builders(self
, query
, *args
):
27 res
= self
.db
.query(query
, *args
)
30 yield Builder(self
.backend
, row
.id, data
=row
)
33 builders
= self
._get
_builders
("SELECT * FROM builders \
34 WHERE deleted IS FALSE ORDER BY name")
38 def create(self
, name
, user
=None, log
=True):
40 Creates a new builder.
42 builder
= self
._get
_builder
("INSERT INTO builders(name) \
43 VALUES(%s) RETURNING *", name
)
45 # Generate a new passphrase.
46 passphrase
= builder
.regenerate_passphrase()
48 # Log what we have done.
50 builder
.log("created", user
=user
)
52 # The Builder object and the passphrase are returned.
53 return builder
, passphrase
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
):
60 # Search for the hostname in the database.
61 builder
= self
._get
_builder
("SELECT * FROM builders \
62 WHERE name = %s AND deleted IS FALSE", name
)
64 # If the builder was not found or the passphrase does not match,
66 if not builder
or not builder
.validate_passphrase(passphrase
):
69 # Otherwise we return the Builder object.
72 def get_by_id(self
, builder_id
):
73 return self
._get
_builder
("SELECT * FROM builders WHERE id = %s", builder_id
)
75 def get_by_name(self
, name
):
76 return self
._get
_builder
("SELECT * FROM builders \
77 WHERE name = %s AND deleted IS FALSE", name
)
80 res1
= self
.db
.get("SELECT SUM(max_jobs) AS max_jobs FROM builders \
81 WHERE enabled IS TRUE and deleted IS FALSE")
83 res2
= self
.db
.get("SELECT COUNT(*) AS count FROM jobs \
84 WHERE state = 'dispatching' OR state = 'running' OR state = 'uploading'")
87 return (res2
.count
* 100 / res1
.max_jobs
)
91 def get_history(self
, limit
=None, offset
=None, builder
=None, user
=None):
92 query
= "SELECT * FROM builders_history"
98 conditions
.append("builder_id = %s")
99 args
.append(builder
.id)
102 conditions
.append("user_id = %s")
106 query
+= " WHERE %s" % " AND ".join(conditions
)
108 query
+= " ORDER BY time DESC"
112 query
+= " LIMIT %s,%s"
113 args
+= [offset
, limit
,]
119 for entry
in self
.db
.query(query
, *args
):
120 entry
= logs
.BuilderLogEntry(self
.pakfire
, entry
)
121 entries
.append(entry
)
126 class Builder(base
.DataObject
):
129 def __eq__(self
, other
):
130 if isinstance(other
, self
.__class
__):
131 return self
.id == other
.id
133 def __lt__(self
, other
):
134 if isinstance(other
, self
.__class
__):
135 return self
.name
< other
.name
137 def log(self
, action
, user
=None):
142 self
.db
.execute("INSERT INTO builders_history(builder_id, action, user_id, time) \
143 VALUES(%s, %s, %s, NOW())", self
.id, action
, user_id
)
145 def regenerate_passphrase(self
):
147 Generates a new random passphrase and stores it as a salted hash
150 The new passphrase is returned to be sent to the user (once).
152 # Generate a random string with 40 chars.
153 passphrase
= generate_random_string(length
=40)
155 # Create salted hash.
156 passphrase_hash
= generate_password_hash(passphrase
)
158 # Store the hash in the database.
159 self
._set
_attribute
("passphrase", passphrase_hash
)
161 # Return the clear-text passphrase.
164 def validate_passphrase(self
, passphrase
):
166 Compare the given passphrase with the one stored in the database.
168 return check_password_hash(passphrase
, self
.data
.passphrase
)
172 def set_description(self
, description
):
173 self
._set
_attribute
("description", description
)
175 description
= property(lambda s
: s
.data
.description
or "", set_description
)
180 Returns time of last keepalive message from this host.
182 return self
.data
.time_keepalive
184 def update_keepalive(self
, loadavg1
=None, loadavg5
=None, loadavg15
=None,
185 mem_total
=None, mem_free
=None, swap_total
=None, swap_free
=None,
188 Update the keepalive timestamp of this machine.
190 self
.db
.execute("UPDATE builders SET time_keepalive = NOW(), \
191 loadavg1 = %s, loadavg5 = %s, loadavg15 = %s, space_free = %s, \
192 mem_total = %s, mem_free = %s, swap_total = %s, swap_free = %s \
193 WHERE id = %s", loadavg1
, loadavg5
, loadavg15
, space_free
,
194 mem_total
, mem_free
, swap_total
, swap_free
, self
.id)
196 def update_info(self
, cpu_model
=None, cpu_count
=None, cpu_arch
=None, cpu_bogomips
=None,
197 pakfire_version
=None, host_key
=None, os_name
=None):
198 # Update all the rest.
199 self
.db
.execute("UPDATE builders SET time_updated = NOW(), \
200 pakfire_version = %s, cpu_model = %s, cpu_count = %s, cpu_arch = %s, \
201 cpu_bogomips = %s, host_key_id = %s, os_name = %s WHERE id = %s",
202 pakfire_version
, cpu_model
, cpu_count
, cpu_arch
, cpu_bogomips
,
203 host_key
, os_name
, self
.id)
205 def set_enabled(self
, enabled
):
206 self
._set
_attribute
("enabled", enabled
)
208 enabled
= property(lambda s
: s
.data
.enabled
, set_enabled
)
212 return not self
.enabled
215 def native_arch(self
):
217 The native architecture of this builder
222 def supported_arches(self
):
223 # Every builder supports noarch
226 # We can always build our native architeture
228 arches
.append(self
.native_arch
)
230 # Get all compatible architectures
231 res
= self
.db
.query("SELECT build_arch FROM arches_compat \
232 WHERE native_arch = %s", self
.native_arch
)
235 if not row
.build_arch
in arches
:
236 arches
.append(row
.build_arch
)
238 return sorted(arches
)
240 def get_build_release(self
):
241 return self
.data
.build_release
== "Y"
243 def set_build_release(self
, value
):
249 self
.db
.execute("UPDATE builders SET build_release = %s WHERE id = %s",
254 self
._data
["build_release"] = value
256 build_release
= property(get_build_release
, set_build_release
)
258 def get_build_scratch(self
):
259 return self
.data
.build_scratch
== "Y"
261 def set_build_scratch(self
, value
):
267 self
.db
.execute("UPDATE builders SET build_scratch = %s WHERE id = %s",
272 self
._data
["build_scratch"] = value
274 build_scratch
= property(get_build_scratch
, set_build_scratch
)
276 def get_build_test(self
):
277 return self
.data
.build_test
== "Y"
279 def set_build_test(self
, value
):
285 self
.db
.execute("UPDATE builders SET build_test = %s WHERE id = %s",
290 self
._data
["build_test"] = value
292 build_test
= property(get_build_test
, set_build_test
)
295 def build_types(self
):
298 if self
.build_release
:
299 ret
.append("release")
301 if self
.build_scratch
:
302 ret
.append("scratch")
309 def set_max_jobs(self
, value
):
310 self
._set
_attribute
("max_jobs", value
)
312 max_jobs
= property(lambda s
: s
.data
.max_jobs
, set_max_jobs
)
316 return self
.data
.name
323 def passphrase(self
):
324 return self
.data
.passphrase
330 return ", ".join(["%.2f" % l
for l
in (self
.loadavg1
, self
.loadavg5
, self
.loadavg15
)])
334 return self
.data
.loadavg1
or 0.0
338 return self
.data
.loadavg5
or 0.0
342 return self
.data
.loadavg15
or 0.0
345 def pakfire_version(self
):
346 return self
.data
.pakfire_version
or ""
350 return self
.data
.os_name
or ""
354 return self
.data
.cpu_model
or ""
358 return self
.data
.cpu_count
362 return self
.data
.cpu_arch
365 def cpu_bogomips(self
):
366 return self
.data
.cpu_bogomips
or 0.0
369 def mem_percentage(self
):
370 if not self
.mem_total
:
373 return self
.mem_used
* 100 / self
.mem_total
377 return self
.data
.mem_total
381 if self
.mem_total
and self
.mem_free
:
382 return self
.mem_total
- self
.mem_free
386 return self
.data
.mem_free
389 def swap_percentage(self
):
390 if not self
.swap_total
:
393 return self
.swap_used
* 100 / self
.swap_total
396 def swap_total(self
):
397 return self
.data
.swap_total
401 if self
.swap_total
and self
.swap_free
:
402 return self
.swap_total
- self
.swap_free
406 return self
.data
.swap_free
409 def space_free(self
):
410 return self
.data
.space_free
414 if not self
.cpu_count
or not self
.loadavg1
:
417 return self
.loadavg1
>= self
.cpu_count
420 def host_key_id(self
):
421 return self
.data
.host_key_id
428 if self
.data
.time_keepalive
is None:
431 #if self.data.updated >= 5*60:
437 def active_jobs(self
, *args
, **kwargs
):
438 return self
.pakfire
.jobs
.get_active(builder
=self
, *args
, **kwargs
)
441 def too_many_jobs(self
):
443 Tell if this host is already running enough or too many jobs.
445 return len(self
.active_jobs
) >= self
.max_jobs
447 def get_next_jobs(self
, limit
=None):
449 Returns a list of jobs that can be built on this host.
451 return self
.pakfire
.jobs
.get_next(arches
=self
.buildable_arches
, limit
=limit
)
453 def get_next_job(self
):
455 Returns the next job in line for this builder.
457 # Get the first item of all jobs in the list.
458 jobs
= self
.pakfire
.jobs
.get_next(builder
=self
, state
="pending", limit
=1)
463 def get_history(self
, *args
, **kwargs
):
464 kwargs
["builder"] = self
466 return self
.pakfire
.builders
.get_history(*args
, **kwargs
)