from abc import ABC, abstractmethod from data.database import BUILD_PHASE class BuildJobError(Exception): """Base error for build jobs.""" class BuildJobAlreadyExistsError(BuildJobError): """Raised when trying to create a job that already exists.""" class BuildJobDoesNotExistsError(BuildJobError): """Raised when trying to delete a job that does not exists.""" class BuildJobExpiredError(BuildJobError): """Raised when trying to update a job that has already expired.""" class BuildJobResult(object): """ Build job result enum. """ ERROR = "error" EXPIRED = "expired" COMPLETE = "complete" INCOMPLETE = "incomplete" CANCELLED = "cancelled" RESULT_PHASES = { BuildJobResult.ERROR: BUILD_PHASE.ERROR, BuildJobResult.EXPIRED: BUILD_PHASE.INTERNAL_ERROR, BuildJobResult.COMPLETE: BUILD_PHASE.COMPLETE, BuildJobResult.INCOMPLETE: BUILD_PHASE.INTERNAL_ERROR, BuildJobResult.CANCELLED: BUILD_PHASE.CANCELLED, } class BuildStateInterface(ABC): @abstractmethod def create_job(self, build_id, build_metadata): """Creates a new job for a build. Raises an error if the job already exists. Otherwise, returns a job_id. The job should get expired if the job is not scheduled within a time frame. """ @abstractmethod def job_scheduled(self, job_id, execution_id, max_startup_time): """Mark the job as scheduled with execution_id. If the job is not started after max_startup_time, the job should get expired. """ @abstractmethod def job_unschedulable(self, job_id): """Delete job from state tracking. Called when unable to schedule the job. Raises BuildJobError if already called when the job has been successfully scheduled. """ @abstractmethod def on_job_complete(self, build_job, job_result, executor_name, execution_id): """Invoke the callback to clean up the build execution: - Update the build phase - Set the final build job state: complete, cancelled, user_error, internal_error, cancelled - Clean up any resources used to execute the build: ec2 instances, k8s jobs, ... Returns False if there is an error, and should be tried again. """ @abstractmethod def start_job(self, job_id): """Mark a job as started. Returns False if the job does not exists, or has already started. """ @abstractmethod def update_job_phase(self, job_id, phase): """Update the given job phase. Returns False if the given job does not exists or the job has been cancelled. """ @abstractmethod def job_heartbeat(self, job_id): """Sends a heartbeat to a job, extending its expiration time. Returns True if the given job exists and its expiration was updated, False otherwise. """ @abstractmethod def cancel_build(self, build_id): """ Cancels the given build. """