feat: 增加跳过的主机统计, 增加每台主机执行情况api

pull/9169/head
Aaron3S 2022-12-07 20:13:26 +08:00
parent 66bdc375df
commit c14b97419d
5 changed files with 83 additions and 10 deletions

View File

@ -10,7 +10,7 @@ __all__ = ['JMSInventory']
class JMSInventory: class JMSInventory:
def __init__(self, assets, account_policy='privileged_first', def __init__(self, assets, account_policy='privileged_first',
account_prefer='root,Administrator', host_callback=None): account_prefer='root,Administrator', host_callback=None, unique_host_name=False):
""" """
:param assets: :param assets:
:param account_prefer: account username name if not set use account_policy :param account_prefer: account username name if not set use account_policy
@ -20,6 +20,8 @@ class JMSInventory:
self.account_prefer = account_prefer self.account_prefer = account_prefer
self.account_policy = account_policy self.account_policy = account_policy
self.host_callback = host_callback self.host_callback = host_callback
self.exclude_hosts = {}
self.unique_host_name = unique_host_name
@staticmethod @staticmethod
def clean_assets(assets): def clean_assets(assets):
@ -112,6 +114,9 @@ class JMSInventory:
'secret': account.secret, 'secret_type': account.secret_type 'secret': account.secret, 'secret_type': account.secret_type
} if account else None } if account else None
} }
if self.unique_host_name:
host['name'] += '({})'.format(asset.id)
if host['jms_account'] and asset.platform.type == 'oracle': if host['jms_account'] and asset.platform.type == 'oracle':
host['jms_account']['mode'] = 'sysdba' if account.privileged else None host['jms_account']['mode'] = 'sysdba' if account.privileged else None
@ -194,7 +199,7 @@ class JMSInventory:
print(_("Skip hosts below:")) print(_("Skip hosts below:"))
for i, host in enumerate(exclude_hosts, start=1): for i, host in enumerate(exclude_hosts, start=1):
print("{}: [{}] \t{}".format(i, host['name'], host['error'])) print("{}: [{}] \t{}".format(i, host['name'], host['error']))
self.exclude_hosts[host['name']] = host['error']
hosts = list(filter(lambda x: not x.get('error'), hosts)) hosts = list(filter(lambda x: not x.get('error'), hosts))
data = {'all': {'hosts': {}}} data = {'all': {'hosts': {}}}
for host in hosts: for host in hosts:

View File

@ -1,12 +1,12 @@
from rest_framework.views import APIView from rest_framework.views import APIView
from django.shortcuts import get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from ops.api.base import SelfBulkModelViewSet from ops.api.base import SelfBulkModelViewSet
from ops.models import Job, JobExecution from ops.models import Job, JobExecution
from ops.serializers.job import JobSerializer, JobExecutionSerializer from ops.serializers.job import JobSerializer, JobExecutionSerializer
__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView'] __all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail']
from ops.tasks import run_ops_job_execution from ops.tasks import run_ops_job_execution
from ops.variables import JMS_JOB_VARIABLE_HELP from ops.variables import JMS_JOB_VARIABLE_HELP
@ -73,3 +73,14 @@ class JobRunVariableHelpAPIView(APIView):
def get(self, request, **kwargs): def get(self, request, **kwargs):
return Response(data=JMS_JOB_VARIABLE_HELP) return Response(data=JMS_JOB_VARIABLE_HELP)
class JobAssetDetail(APIView):
rbac_perms = ()
permission_classes = ()
def get(self, request, **kwargs):
execution_id = request.query_params.get('execution_id')
if execution_id:
execution = get_object_or_404(JobExecution, id=execution_id)
return Response(data=execution.assent_result_detail)

View File

@ -88,7 +88,7 @@ class Job(JMSBaseModel, PeriodTaskModelMixin):
@property @property
def inventory(self): def inventory(self):
return JMSInventory(self.assets.all(), self.runas_policy, self.runas) return JMSInventory(self.assets.all(), self.runas_policy, self.runas, unique_host_name=True)
def create_execution(self): def create_execution(self):
return self.executions.create() return self.executions.create()
@ -110,6 +110,55 @@ class JobExecution(JMSBaseModel):
date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True)
date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished"))
@property
def count(self):
if self.is_finished and not self.summary.get('error', None):
return {
"ok": len(self.summary['ok']),
"failed": len(self.summary['failures']) + len(self.summary['dark']),
"excludes": len(self.summary['excludes']),
"total": self.job.assets.count()
}
@property
def assent_result_detail(self):
if self.is_finished and not self.summary.get('error', None):
result = {
"summary": self.count,
"detail": [],
}
for asset in self.job.assets.all():
asset_detail = {
"name": asset.name,
"status": "ok",
"tasks": [],
}
host_name = "{}({})".format(asset.name, asset.id)
if self.summary["excludes"].get(host_name, None):
asset_detail.update({"status": "excludes"})
result["detail"].append(asset_detail)
break
if self.result["dark"].get(host_name, None):
asset_detail.update({"status": "failed"})
for key, task in self.result["dark"][host_name].items():
task_detail = {"name": key,
"output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))}
asset_detail["tasks"].append(task_detail)
if self.result["failures"].get(host_name, None):
asset_detail.update({"status": "failed"})
for key, task in self.result["failures"][host_name].items():
task_detail = {"name": key,
"output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))}
asset_detail["tasks"].append(task_detail)
if self.result["ok"].get(host_name, None):
for key, task in self.result["ok"][host_name].items():
task_detail = {"name": key,
"output": "{}{}".format(task.get("stdout", ""), task.get("stderr", ""))}
asset_detail["tasks"].append(task_detail)
result["detail"].append(asset_detail)
return result
@property @property
def job_type(self): def job_type(self):
return self.job.type return self.job.type
@ -124,6 +173,11 @@ class JobExecution(JMSBaseModel):
def get_runner(self): def get_runner(self):
inv = self.job.inventory inv = self.job.inventory
inv.write_to_file(self.inventory_path) inv.write_to_file(self.inventory_path)
if len(inv.exclude_hosts) > 0:
self.summary['excludes'] = inv.exclude_hosts
self.result['excludes'] = inv.exclude_hosts
self.save()
if isinstance(self.parameters, str): if isinstance(self.parameters, str):
extra_vars = json.loads(self.parameters) extra_vars = json.loads(self.parameters)
else: else:
@ -191,7 +245,7 @@ class JobExecution(JMSBaseModel):
def set_error(self, error): def set_error(self, error):
this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时 this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时
this.status = 'failed' this.status = 'failed'
this.summary['error'] = str(error) this.summary.update({'error': str(error)})
this.finish_task() this.finish_task()
def set_result(self, cb): def set_result(self, cb):
@ -200,8 +254,8 @@ class JobExecution(JMSBaseModel):
} }
this = self.__class__.objects.get(id=self.id) this = self.__class__.objects.get(id=self.id)
this.status = status_mapper.get(cb.status, cb.status) this.status = status_mapper.get(cb.status, cb.status)
this.summary = cb.summary this.summary.update(cb.summary)
this.result = cb.result this.result.update(cb.result)
this.finish_task() this.finish_task()
def finish_task(self): def finish_task(self):

View File

@ -28,10 +28,13 @@ class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin):
class JobExecutionSerializer(serializers.ModelSerializer): class JobExecutionSerializer(serializers.ModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
job_type = serializers.ReadOnlyField(label=_("Job type")) job_type = serializers.ReadOnlyField(label=_("Job type"))
count = serializers.ReadOnlyField(label=_("Count"))
class Meta: class Meta:
model = JobExecution model = JobExecution
read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', read_only_fields = ["id", "task_id", "timedelta", "count", "time_cost", 'is_finished', 'date_start',
'date_finished',
'date_created',
'is_success', 'task_id', 'short_id', 'job_type', 'creator'] 'is_success', 'task_id', 'short_id', 'job_type', 'creator']
fields = read_only_fields + [ fields = read_only_fields + [
"job", "parameters" "job", "parameters"

View File

@ -24,7 +24,7 @@ router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-execut
urlpatterns = [ urlpatterns = [
path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'), path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'),
path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'),
path('ansible/job-execution/<uuid:pk>/log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'), path('ansible/job-execution/<uuid:pk>/log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'),
path('celery/task/<uuid:name>/task-execution/<uuid:pk>/log/', api.CeleryTaskExecutionLogApi.as_view(), path('celery/task/<uuid:name>/task-execution/<uuid:pk>/log/', api.CeleryTaskExecutionLogApi.as_view(),