from . import base
from . import logs
+from .decorators import *
+
from .users import generate_password_hash, check_password_hash, generate_random_string
class Builders(base.Object):
+ def _get_builder(self, query, *args):
+ res = self.db.get(query, *args)
+
+ if res:
+ return Builder(self.backend, res.id, data=res)
+
+ def _get_builders(self, query, *args):
+ res = self.db.query(query, *args)
+
+ for row in res:
+ yield Builder(self.backend, row.id, data=row)
+
+ def __iter__(self):
+ builders = self._get_builders("SELECT * FROM builders \
+ WHERE deleted IS FALSE ORDER BY name")
+
+ return iter(builders)
+
+ def create(self, name, user=None, log=True):
+ """
+ Creates a new builder.
+ """
+ builder = self._get_builder("INSERT INTO builders(name) \
+ VALUES(%s) RETURNING *", name)
+
+ # Generate a new passphrase.
+ passphrase = builder.regenerate_passphrase()
+
+ # Log what we have done.
+ if log:
+ builder.log("created", user=user)
+
+ # The Builder object and the passphrase are returned.
+ return builder, passphrase
+
def auth(self, name, passphrase):
# If either name or passphrase is None, we don't check at all.
if None in (name, passphrase):
return
# Search for the hostname in the database.
- # The builder must not be deleted.
- builder = self.db.get("SELECT id FROM builders WHERE name = %s AND \
- NOT status = 'deleted'", name)
-
- if not builder:
- return
-
- # Get the whole Builder object from the database.
- builder = self.get_by_id(builder.id)
+ builder = self._get_builder("SELECT * FROM builders \
+ WHERE name = %s AND deleted IS FALSE", name)
# If the builder was not found or the passphrase does not match,
# you have bad luck.
# Otherwise we return the Builder object.
return builder
- def get_all(self):
- builders = self.db.query("SELECT * FROM builders WHERE NOT status = 'deleted' ORDER BY name")
-
- return [Builder(self.pakfire, b.id, b) for b in builders]
-
- def get_by_id(self, id):
- if not id:
- return
-
- return Builder(self.pakfire, id)
+ def get_by_id(self, builder_id):
+ return self._get_builder("SELECT * FROM builders WHERE id = %s", builder_id)
def get_by_name(self, name):
- builder = self.db.get("SELECT * FROM builders WHERE name = %s LIMIT 1", name)
-
- if builder:
- return Builder(self.pakfire, builder.id, builder)
-
- def get_all_arches(self):
- arches = set()
-
- for result in self.db.query("SELECT DISTINCT arches FROM builders"):
- if not result.arches:
- continue
-
- _arches = result.arches.split()
-
- for arch in _arches:
- arches.add(arch)
-
- return sorted(arches)
-
- def get_load(self):
- res1 = self.db.get("SELECT SUM(max_jobs) AS max_jobs FROM builders \
- WHERE status = 'enabled'")
-
- res2 = self.db.get("SELECT COUNT(*) AS count FROM jobs \
- WHERE state = 'dispatching' OR state = 'running' OR state = 'uploading'")
-
- try:
- return (res2.count * 100 / res1.max_jobs)
- except:
- return 0
+ return self._get_builder("SELECT * FROM builders \
+ WHERE name = %s AND deleted IS FALSE", name)
def get_history(self, limit=None, offset=None, builder=None, user=None):
query = "SELECT * FROM builders_history"
return entries
-class Builder(base.Object):
- def __init__(self, pakfire, id, data=None):
- base.Object.__init__(self, pakfire)
-
- self.id = id
-
- # Cache.
- self._data = data
- self._active_jobs = None
- self._disabled_arches = None
+class Builder(base.DataObject):
+ table = "builders"
- def __cmp__(self, other):
- if other is None:
- return -1
-
- return cmp(self.id, other.id)
-
- @property
- def data(self):
- if self._data is None:
- self._data = self.db.get("SELECT * FROM builders WHERE id = %s", self.id)
- assert self._data
+ def __eq__(self, other):
+ if isinstance(other, self.__class__):
+ return self.id == other.id
- return self._data
-
- @classmethod
- def create(cls, pakfire, name, user=None, log=True):
- """
- Creates a new builder.
- """
- builder_id = pakfire.db.execute("INSERT INTO builders(name, time_created) \
- VALUES(%s, NOW())", name)
-
- # Create Builder object.
- builder = cls(pakfire, builder_id)
-
- # Generate a new passphrase.
- passphrase = builder.regenerate_passphrase()
-
- # Log what we have done.
- if log:
- builder.log("created", user=user)
-
- # The Builder object and the passphrase are returned.
- return builder, passphrase
+ def __lt__(self, other):
+ if isinstance(other, self.__class__):
+ return self.name < other.name
def log(self, action, user=None):
user_id = None
self.db.execute("INSERT INTO builders_history(builder_id, action, user_id, time) \
VALUES(%s, %s, %s, NOW())", self.id, action, user_id)
- def set(self, key, value):
- self.db.execute("UPDATE builders SET %s = %%s WHERE id = %%s LIMIT 1" % key,
- value, self.id)
- self.data[key] = value
-
def regenerate_passphrase(self):
"""
Generates a new random passphrase and stores it as a salted hash
The new passphrase is returned to be sent to the user (once).
"""
- # Generate a random string with 20 chars.
- passphrase = generate_random_string(length=20)
+ # Generate a random string with 40 chars.
+ passphrase = generate_random_string(length=40)
# Create salted hash.
passphrase_hash = generate_password_hash(passphrase)
# Store the hash in the database.
- self.db.execute("UPDATE builders SET passphrase = %s WHERE id = %s",
- passphrase_hash, self.id)
+ self._set_attribute("passphrase", passphrase_hash)
# Return the clear-text passphrase.
return passphrase
"""
return check_password_hash(passphrase, self.data.passphrase)
- @property
- def description(self):
- return self.data.description or ""
-
- @property
- def status(self):
- return self.data.status
+ # Description
- def update_description(self, description):
- self.db.execute("UPDATE builders SET description = %s, time_updated = NOW() \
- WHERE id = %s", description, self.id)
+ def set_description(self, description):
+ self._set_attribute("description", description)
- if self._data:
- self._data["description"] = description
+ description = property(lambda s: s.data.description or "", set_description)
@property
def keepalive(self):
pakfire_version, cpu_model, cpu_count, cpu_arch, cpu_bogomips,
host_key, os_name, self.id)
- def update_arches(self, arches):
- # Get all arches this builder does currently support.
- supported_arches = [a.name for a in self.get_arches()]
-
- # Noarch is always supported.
- if not "noarch" in arches:
- arches.append("noarch")
-
- arches_add = []
- for arch in arches:
- if arch in supported_arches:
- supported_arches.remove(arch)
- continue
-
- arches_add.append(arch)
- arches_rem = supported_arches
-
- for arch_name in arches_add:
- arch = self.pakfire.arches.get_by_name(arch_name)
- if not arch:
- logging.info("Client sent unknown architecture: %s" % arch_name)
- continue
-
- self.db.execute("INSERT INTO builders_arches(builder_id, arch_id) \
- VALUES(%s, %s)", self.id, arch.id)
-
- for arch_name in arches_rem:
- arch = self.pakfire.arches.get_by_name(arch_name)
- assert arch
-
- self.db.execute("DELETE FROM builders_arches WHERE builder_id = %s \
- AND arch_id = %s", self.id, arch.id)
-
- def get_enabled(self):
- return self.status == "enabled"
+ def set_enabled(self, enabled):
+ self._set_attribute("enabled", enabled)
- def set_enabled(self, value):
- # XXX deprecated
-
- if value:
- value = "enabled"
- else:
- value = "disabled"
-
- self.set_status(value)
-
- enabled = property(get_enabled, set_enabled)
+ enabled = property(lambda s: s.data.enabled, set_enabled)
@property
def disabled(self):
return not self.enabled
- def set_status(self, status, user=None, log=True):
- assert status in ("created", "enabled", "disabled", "deleted")
-
- if self.status == status:
- return
-
- self.db.execute("UPDATE builders SET status = %s WHERE id = %s",
- status, self.id)
-
- if self._data:
- self._data["status"] = status
-
- if log:
- self.log(status, user=user)
-
@property
- def arches(self):
- if not hasattr(self, "_arches"):
- self._arches = []
-
- if self.cpu_arch:
- res = self.db.query("SELECT build_arch FROM arches_compat \
- WHERE host_arch = %s", self.cpu_arch)
-
- self._arches += [r.build_arch for r in res]
- if not self.cpu_arch in self._arches:
- self._arches.append(self.cpu_arch)
-
- return self._arches
-
- def get_build_release(self):
- return self.data.build_release == "Y"
-
- def set_build_release(self, value):
- if value:
- value = "Y"
- else:
- value = "N"
-
- self.db.execute("UPDATE builders SET build_release = %s WHERE id = %s",
- value, self.id)
-
- # Update the cache.
- if self._data:
- self._data["build_release"] = value
-
- build_release = property(get_build_release, set_build_release)
-
- def get_build_scratch(self):
- return self.data.build_scratch == "Y"
-
- def set_build_scratch(self, value):
- if value:
- value = "Y"
- else:
- value = "N"
-
- self.db.execute("UPDATE builders SET build_scratch = %s WHERE id = %s",
- value, self.id)
-
- # Update the cache.
- if self._data:
- self._data["build_scratch"] = value
-
- build_scratch = property(get_build_scratch, set_build_scratch)
-
- def get_build_test(self):
- return self.data.build_test == "Y"
-
- def set_build_test(self, value):
- if value:
- value = "Y"
- else:
- value = "N"
-
- self.db.execute("UPDATE builders SET build_test = %s WHERE id = %s",
- value, self.id)
-
- # Update the cache.
- if self._data:
- self._data["build_test"] = value
+ def native_arch(self):
+ """
+ The native architecture of this builder
+ """
+ return self.cpu_arch
- build_test = property(get_build_test, set_build_test)
+ @lazy_property
+ def supported_arches(self):
+ # Every builder supports noarch
+ arches = ["noarch"]
- @property
- def build_types(self):
- ret = []
+ # We can always build our native architeture
+ if self.native_arch:
+ arches.append(self.native_arch)
- if self.build_release:
- ret.append("release")
+ # Get all compatible architectures
+ res = self.db.query("SELECT build_arch FROM arches_compat \
+ WHERE native_arch = %s", self.native_arch)
- if self.build_scratch:
- ret.append("scratch")
+ for row in res:
+ if not row.build_arch in arches:
+ arches.append(row.build_arch)
- if self.build_test:
- ret.append("test")
+ return sorted(arches)
- return ret
+ def set_testmode(self, testmode):
+ self._set_attribute("testmode", testmode)
- def get_max_jobs(self):
- return self.data.max_jobs
+ testmode = property(lambda s: s.data.testmode, set_testmode)
def set_max_jobs(self, value):
- self.set("max_jobs", value)
+ self._set_attribute("max_jobs", value)
- max_jobs = property(get_max_jobs, set_max_jobs)
+ max_jobs = property(lambda s: s.data.max_jobs, set_max_jobs)
@property
def name(self):
def space_free(self):
return self.data.space_free
- @property
- def overload(self):
- if not self.cpu_count or not self.loadavg1:
- return None
-
- return self.loadavg1 >= self.cpu_count
-
@property
def host_key_id(self):
return self.data.host_key_id
return "online"
- def get_active_jobs(self, *args, **kwargs):
- if self._active_jobs is None:
- self._active_jobs = self.pakfire.jobs.get_active(builder=self, *args, **kwargs)
-
- return self._active_jobs
-
- def count_active_jobs(self):
- return len(self.get_active_jobs())
+ @lazy_property
+ def active_jobs(self, *args, **kwargs):
+ return self.pakfire.jobs.get_active(builder=self, *args, **kwargs)
@property
def too_many_jobs(self):
"""
Tell if this host is already running enough or too many jobs.
"""
- return self.count_active_jobs() >= self.max_jobs
+ return len(self.active_jobs) >= self.max_jobs
- def get_next_jobs(self, limit=None):
- """
- Returns a list of jobs that can be built on this host.
- """
- return self.pakfire.jobs.get_next(arches=self.arches, limit=limit)
+ @lazy_property
+ def jobqueue(self):
+ return self.backend.jobqueue.for_arches(self.supported_arches)
def get_next_job(self):
"""
Returns the next job in line for this builder.
"""
- # Get the first item of all jobs in the list.
- jobs = self.pakfire.jobs.get_next(builder=self, state="pending", limit=1)
+ # Don't send any jobs to disabled builders
+ if not self.enabled:
+ return
+
+ # Don't return anything if the builder has already too many jobs running
+ if self.too_many_jobs:
+ return
+
+ for job in self.jobqueue:
+ # Only allow building test jobs in test mode
+ if self.testmode and not job.test:
+ continue
- if jobs:
- return jobs[0]
+ return job
def get_history(self, *args, **kwargs):
kwargs["builder"] = self