import logging import os.path import uuid from django.conf import settings from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from orgs.mixins.models import JMSOrgBaseModel from ..ansible.inventory import JMSInventory from ..mixin import PeriodTaskModelMixin class BaseAnsibleJob(PeriodTaskModelMixin, JMSOrgBaseModel): owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) account = models.CharField(max_length=128, default='root', verbose_name=_('Account')) account_policy = models.CharField(max_length=128, default='root', verbose_name=_('Account policy')) last_execution = models.ForeignKey('BaseAnsibleExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True) date_last_run = models.DateTimeField(null=True, verbose_name=_('Date last run')) class Meta: abstract = True @property def inventory(self): inv = JMSInventory(self.assets.all(), self.account, self.account_policy) return inv def get_register_task(self): raise NotImplementedError def to_json(self): raise NotImplementedError def create_execution(self): execution = self.executions.create() return execution def run(self, *args, **kwargs): execution = self.create_execution() return execution.start() class BaseAnsibleExecution(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') task = models.ForeignKey(BaseAnsibleJob, on_delete=models.CASCADE, related_name='executions', null=True) result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) summary = models.JSONField(default=dict, verbose_name=_('Summary')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) class Meta: abstract = True ordering = ["-date_start"] def __str__(self): return str(self.id) @property def private_dir(self): uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id return os.path.join(settings.ANSIBLE_DIR, self.task.name, uniq) @property def inventory_path(self): return os.path.join(self.private_dir, 'inventory', 'hosts') def get_runner(self): raise NotImplementedError def finish_task(self): self.date_finished = timezone.now() self.save(update_fields=['result', 'status', 'summary', 'date_finished']) self.update_task() def set_error(self, error): this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时 this.status = 'failed' this.summary['error'] = str(error) this.finish_task() def set_result(self, cb): status_mapper = { 'successful': 'success', } this = self.__class__.objects.get(id=self.id) this.status = status_mapper.get(cb.status, cb.status) this.summary = cb.summary this.result = cb.result this.finish_task() print("Finished") def update_task(self): self.task.last_execution = self self.task.date_last_run = timezone.now() self.task.save(update_fields=['last_execution', 'date_last_run']) def start(self, **kwargs): runner = self.get_runner() try: cb = runner.run(**kwargs) self.set_result(cb) return cb except Exception as e: logging.error(e, exc_info=True) self.set_error(e) @property def is_finished(self): return self.status in ['success', 'failed'] @property def is_success(self): return self.status == 'success' @property def short_id(self): return str(self.id).split('-')[-1] @property def timedelta(self): if self.date_start and self.date_finished: return self.date_finished - self.date_start return None @property def time_cost(self): if self.date_finished and self.date_start: return (self.date_finished - self.date_start).total_seconds() return None