]> git.ipfire.org Git - people/jschlag/pbs.git/blobdiff - src/buildservice/builders.py
Merge branch 'master' of git://git.ipfire.org/pbs
[people/jschlag/pbs.git] / src / buildservice / builders.py
index 5417ea88a3ebd12e02cc82964bfde00cfcf58f2a..861e324e2651f6370881a9c9544e3650f41dbd9b 100644 (file)
@@ -12,24 +12,54 @@ import time
 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.
@@ -39,48 +69,12 @@ class Builders(base.Object):
                # 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"
@@ -117,51 +111,16 @@ class Builders(base.Object):
                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
@@ -171,11 +130,6 @@ class Builder(base.Object):
                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
@@ -183,15 +137,14 @@ class Builder(base.Object):
 
                        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
@@ -202,20 +155,12 @@ class Builder(base.Object):
                """
                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):
@@ -245,164 +190,50 @@ class Builder(base.Object):
                        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):
@@ -502,13 +333,6 @@ class Builder(base.Object):
        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
@@ -526,37 +350,39 @@ class Builder(base.Object):
 
                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