#!/usr/bin/env python # -*- coding: utf-8 -*- # # This file is subject to the terms and conditions defined in # file 'LICENSE.md', which is part of this source code package. # import time from kubernetes_py.K8sContainer import K8sContainer from kubernetes_py.K8sExceptions import CronJobAlreadyRunningException, CronJobRunException, TimedOutException from kubernetes_py.K8sObject import K8sObject from kubernetes_py.K8sPod import K8sPod from kubernetes_py.K8sVolume import K8sVolume from kubernetes_py.models.v1beta1.CronJob import CronJob from kubernetes_py.utils import is_valid_list, is_reachable, sanitize_version_number class K8sCronJob(K8sObject): POD_RUN_WAIT_TIMEOUT_SECONDS = 600 def __init__(self, config=None, name=None): temp = K8sObject(config=config, obj_type="Pod", name="temp") _type = "CronJob" if config and is_reachable(config): v = temp.server_version() if sanitize_version_number(v["major"]) == 1 and sanitize_version_number(v["minor"]) == 4: _type = "ScheduledJob" super(K8sCronJob, self).__init__(config=config, obj_type=_type, name=name) if config is not None and config.pull_secret is not None: self.add_image_pull_secrets(config.pull_secret) # ------------------------------------------------------------------------------------- override def create(self): super(K8sCronJob, self).create() self.get() return self def update(self): super(K8sCronJob, self).update() self.get() return self def list(self, pattern=None, labels=None): ls = super(K8sCronJob, self).list(labels=labels) jobs = list(map(lambda x: CronJob(x), ls)) if pattern is not None: jobs = list(filter(lambda x: pattern in x.name, jobs)) k8s = [] for x in jobs: j = K8sCronJob(config=self.config, name=x.name).from_model(m=x) k8s.append(j) return k8s # ------------------------------------------------------------------------------------- get def get(self): self.model = CronJob(self.get_model()) return self # ------------------------------------------------------------------------------------- add def add_container(self, container=None): if not isinstance(container, K8sContainer): raise SyntaxError("K8sCronJob.add_container() container: [ {0} ] is invalid.".format(container)) containers = self.model.spec.job_template.spec.template.spec.containers if containers is None: containers = [] filtered = list(filter(lambda x: x.name != container.name, containers)) filtered.append(container.model) self.model.spec.job_template.spec.template.spec.containers = filtered return self def add_image_pull_secrets(self, secrets=None): self.model.spec.job_template.spec.template.spec.add_image_pull_secrets(secrets) return self def add_pod_label(self, k=None, v=None): labels = self.model.spec.job_template.spec.template.metadata.labels if labels is None: labels = {} labels.update({k: v}) self.model.spec.job_template.spec.template.metadata.labels = labels return self def add_volume(self, volume=None): if not isinstance(volume, K8sVolume): raise SyntaxError("K8sCronJob.add_volume() volume: [ {0} ] is invalid.".format(volume)) volumes = self.model.spec.job_template.spec.template.spec.volumes if volume.model not in volumes: volumes.append(volume.model) self.model.spec.job_template.spec.template.spec.volumes = volumes return self # ------------------------------------------------------------------------------------- del def del_pod_label(self, k=None): labels = self.model.spec.job_template.spec.template.metadata.labels if k in labels: labels.pop(k) self.model.spec.job_template.spec.template.metadata.labels = labels return self # ------------------------------------------------------------------------------------- schedule @property def schedule(self): return self.model.spec.schedule @schedule.setter def schedule(self, s=None): self.model.spec.schedule = s # ------------------------------------------------------------------------------------- startingDeadlineSeconds @property def starting_deadline_seconds(self): return self.model.spec.starting_deadline_seconds @starting_deadline_seconds.setter def starting_deadline_seconds(self, s=None): self.model.spec.starting_deadline_seconds = s # ------------------------------------------------------------------------------------- activeDeadlineSeconds @property def active_deadline_seconds(self): return self.model.spec.job_template.spec.active_deadline_seconds @active_deadline_seconds.setter def active_deadline_seconds(self, s=None): self.model.spec.job_template.spec.active_deadline_seconds = s # ------------------------------------------------------------------------------------- concurrencyPolicy @property def concurrency_policy(self): return self.model.spec.concurrency_policy @concurrency_policy.setter def concurrency_policy(self, cp=None): self.model.spec.concurrency_policy = cp # ------------------------------------------------------------------------------------- suspend @property def suspend(self): return self.model.spec.suspend @suspend.setter def suspend(self, s=None): self.model.spec.suspend = s # ------------------------------------------------------------------------------------- parallelism @property def parallelism(self): return self.model.spec.job_template.spec.parallelism @parallelism.setter def parallelism(self, jp=None): self.model.spec.job_template.spec.parallelism = jp # ------------------------------------------------------------------------------------- completions @property def completions(self): return self.model.spec.job_template.spec.completions @completions.setter def completions(self, c=None): self.model.spec.job_template.spec.completions = c # ------------------------------------------------------------------------------------- containers @property def containers(self): objs = [] for c in self.model.spec.job_template.spec.template.spec.containers: k8scontainer = K8sContainer(name=c.name, image=c.image) k8scontainer.model = c objs.append(k8scontainer) return objs @containers.setter def containers(self, containers=None): if not is_valid_list(containers, K8sContainer): raise SyntaxError("K8sCronJob: containers: [ {} ] is invalid.".format(containers)) models = [] for obj in containers: models.append(obj.model) self.model.spec.job_template.spec.template.spec.containers = models # ------------------------------------------------------------------------------------- container_image @property def container_image(self): data = {} for c in self.containers: data[c.name] = c.image return data @container_image.setter def container_image(self, tup=None): if not isinstance(tup, tuple): raise SyntaxError("K8sCronJob.container_image() must be a tuple of the form (name, image)") name, image = tup found = list(filter(lambda x: x.name == name, self.containers)) if found: new = list(filter(lambda x: x.name != name, self.containers)) found[0].image = image new.append(found[0]) self.containers = new # ------------------------------------------------------------------------------------- dnsPolicy @property def dns_policy(self): return self.model.spec.template.spec.dns_policy @dns_policy.setter def dns_policy(self, policy=None): if policy not in self.model.spec.job_template.spec.template.spec.VALID_DNS_POLICIES: raise SyntaxError( "K8sJob: dns_policy: [ {} ] is invalid, expected [ {} ].".format( policy, self.model.spec.job_template.spec.template.spec.VALID_DNS_POLICIES ) ) self.model.spec.job_template.spec.template.spec.dns_policy = policy # ------------------------------------------------------------------------------------- restartPolicy @property def restart_policy(self): return self.model.spec.job_template.spec.template.spec.restart_policy @restart_policy.setter def restart_policy(self, p=None): self.model.spec.job_template.spec.template.spec.restart_policy = p # ------------------------------------------------------------------------------------- lastScheduleTime @property def last_schedule_time(self): return self.model.status.last_schedule_time @last_schedule_time.setter def last_schedule_time(self, t=None): raise NotImplementedError("K8sCronJob: last_schedule_time is read-only.") # ------------------------------------------------------------------------------------- active @property def active(self): return self.status.active @active.setter def active(self, a=None): raise NotImplementedError("K8sCronJob: active is read-only.") # ------------------------------------------------------------------------------------- pod @property def pod(self): podspec = self.model.spec.job_template.spec.template.spec name = "{}-{}".format(self.name, int(time.time())) pod = K8sPod(config=self.config, name=name) pod.model.spec = podspec return pod @pod.setter def pod(self, p=None): raise NotImplementedError("K8sCronjob: pod is read-only.") # ------------------------------------------------------------------------------------- pod labels @property def pod_labels(self): return self.model.spec.job_template.spec.template.metadata.labels @pod_labels.setter def pod_labels(self, labels=None): self.model.spec.job_template.spec.template.metadata.labels = labels # ------------------------------------------------------------------------------------- image pull secrets @property def image_pull_secrets(self): return self.model.spec.job_template.spec.template.spec.image_pull_secrets @image_pull_secrets.setter def image_pull_secrets(self, secrets=None): self.model.spec.job_template.spec.template.spec.image_pull_secrets = secrets # ------------------------------------------------------------------------------------- run def run(self, timeout=POD_RUN_WAIT_TIMEOUT_SECONDS): """ Forces a K8sCronJob to run immediately. - Fail if the K8sCronJob is currently running on-schedule. - Suspend the K8sCronJob. - Spawn a K8sPod. - Unsuspend a K8sCronJob. :param timeout: The timeout, in seconds, after which to kill the K8sPod. :return: None. """ if not isinstance(timeout, int): raise SyntaxError("K8sCronJob.run() timeout: [ {} ] is invalid.") if len(self.active): raise CronJobAlreadyRunningException( "K8sCronJob.run() failed: CronJob: [ {} ] " "has [ {} ] active Jobs currently.".format(self.name, len(self.active)) ) self.suspend = True self.update() pod = self.pod if timeout: self.POD_RUN_WAIT_TIMEOUT_SECONDS = timeout try: pod.create() start_time = time.time() while pod.phase not in ["Succeeded", "Failed"]: pod.get() time.sleep(2) self._check_timeout(start_time) except Exception as err: raise CronJobRunException("K8sCronJob.run() failed: {}".format(err)) finally: pod.delete() self.suspend = False self.update() def _check_timeout(self, start_time=None): if self.POD_RUN_WAIT_TIMEOUT_SECONDS == -1: return elapsed_time = time.time() - start_time if elapsed_time >= self.POD_RUN_WAIT_TIMEOUT_SECONDS: # timeout raise TimedOutException("Timed out running one-off CronJob: [ {} ]".format(self.name))