diff --git a/backend/copr_backend/background_worker_build.py b/backend/copr_backend/background_worker_build.py index 1cf04eb91..2f449f135 100644 --- a/backend/copr_backend/background_worker_build.py +++ b/backend/copr_backend/background_worker_build.py @@ -10,6 +10,7 @@ import time import json +from datetime import datetime, timedelta from packaging import version from copr_common.enums import StatusEnum @@ -766,6 +767,36 @@ def build(self, attempt): if transfer_failure: raise BuildRetry("SSH problems when downloading live log: {}" .format(transfer_failure)) + + if self.job.allow_user_ssh: + # TODO Use CancellableThreadTask + + self.log.info("Keeping builder alive for user SSH") + while True: + started = datetime.fromtimestamp(self.job.started_on) + # expiration = started + timedelta(minutes=30) + expiration = started + timedelta(minutes=5) + + cmd = "cat /run/copr-builder-expiration" + rc, out, err = self.ssh.run_expensive(cmd) + if rc == 0: + try: + expiration = datetime.fromtimestamp(float(out)) + except ValueError: + pass + + if datetime.now() > expiration: + break + + # TODO break if uptime of the instance exceeded our max limit + # It can't actually be uptime but rather time after the build + # finishes. Or ... uptime, but we need to mention it in help + # so that kernel packagers know to kill the rpmbuild and run it + # manually ASAP or there won't be enough time. + + time.sleep(60) + + # TODO Should the results be downloaded for jobs with user SSH? self._download_results() self._drop_host() diff --git a/backend/copr_backend/job.py b/backend/copr_backend/job.py index 76046852a..38663da80 100644 --- a/backend/copr_backend/job.py +++ b/backend/copr_backend/job.py @@ -72,6 +72,7 @@ def __init__(self, task_data, worker_opts): self.results = None self.appstream = None + self.allow_user_ssh = None # TODO: validate update data for key, val in task_data.items(): diff --git a/rpmbuild/bin/copr-builder b/rpmbuild/bin/copr-builder index dcfc699e5..130290db1 100755 --- a/rpmbuild/bin/copr-builder +++ b/rpmbuild/bin/copr-builder @@ -19,6 +19,8 @@ I have no idea how to implement them: import argparse +from datetime import datetime, timedelta +from contextlib import suppress def cmd_help(): @@ -28,6 +30,7 @@ def cmd_help(): like `if $(ssh stroj cat foo|head -n1) == "foo"`. So instead, we instruct users to run `copr-builder help` manually. """ + # TODO Should this be in the documentation instead? print( "You have been entrusted with root access to a Copr builder.\n" "Please be responsible.\n" @@ -60,28 +63,77 @@ def cmd_help(): ) -def cmd_show(): +class CMDShow: """ TODO """ - pass - - -def cmd_prolong(): + # We can copy a JSON task file from backend or create it in copr-rpmbuild + # Otherwise we don't have that much information to show + # We can maybe have --keep-ssh parameter for copr-rpmbuild. Which would dump + # the file. + # + # We can show right now: + # - Remaining time for the instance + # - PID of the currently running copr-rpmbuild process + # We want to show but don't have enough information: + # - Build ID for which the instance was spawned + # - The user for which the instance was spawned + + def run(self): + print("Current copr-rpmbuild PID: {0}".format(self.copr_rpmbuild_pid)) + + @property + def copr_rpmbuild_pid(self): + path = "/var/lib/copr-rpmbuild/pid" + with suppress(OSError), open(path, "r") as fp: + pid = fp.read() + if pid.isdecimal(): + return pid + return None + + @property + def uptime_seconds(self): + with open("/proc/uptime", "r") as f: + return float(f.readline().split()[0]) + + @property + def max_uptime_seconds(self): + # uptime_seconds + 48 hours + pass + + @property + def remaining_time(self): + # TODO We are implementing the same thing on backend, move it to common + now = time.time() + path = "/run/copr-builder-expiration" + with suppress(OSError), open(path, "r") as fp: + foo = fp.read() + + # TODO Max possible + return None + + +def cmd_prolong(args): """ TODO """ - pass + # TODO We shouldn't start from now but rather from previous expiration + expiration = datetime.now() + timedelta(hours=args.hours) + + path = "/run/copr-builder-expiration" + with open(path, "w+") as fp: + fp.write(str(expiration.timestamp())) + print("Prolonged to {0}".format(expiration)) def cmd_release(): """ TODO """ + # TODO The same code as cmd_prolong but set expiration to the past time pass - def get_parser(): parser = argparse.ArgumentParser( "copr-builder", @@ -115,6 +167,7 @@ def get_parser(): parser_prolong.add_argument( "--hours", type=int, + required=True, help="TODO", ) parser_prolong.set_defaults(command="prolong") @@ -141,10 +194,12 @@ def main(): cmd_help() elif args.command == "show": - cmd_show() + # cmd_show() + cmd = CMDShow() + cmd.run() elif args.command == "prolong": - cmd_prolong() + cmd_prolong(args) elif args.command == "release": cmd_release()