[constructed-inventory] Backlink events to real hosts and summaries to both hosts (#13718)

* Backlink events to real hosts and summaries to both hosts

* Prevent error when original host is deleted during job run

* No duplicate entries, review suggestion from Rick

* Change word tense in help text, dict style adjustments

From code review

Co-authored-by: Rick Elrod <rick@elrod.me>

* Back out new variable for constructed host id

---------

Co-authored-by: Rick Elrod <rick@elrod.me>
This commit is contained in:
Alan Rominger
2023-03-22 14:04:25 -04:00
committed by Rick Elrod
parent b88d9f4731
commit 3f5a4cb6f1
4 changed files with 47 additions and 10 deletions

View File

@@ -158,6 +158,7 @@ SUMMARIZABLE_FK_FIELDS = {
'kind', 'kind',
), ),
'host': DEFAULT_SUMMARY_FIELDS, 'host': DEFAULT_SUMMARY_FIELDS,
'constructed_host': DEFAULT_SUMMARY_FIELDS,
'group': DEFAULT_SUMMARY_FIELDS, 'group': DEFAULT_SUMMARY_FIELDS,
'default_environment': DEFAULT_SUMMARY_FIELDS + ('image',), 'default_environment': DEFAULT_SUMMARY_FIELDS + ('image',),
'execution_environment': DEFAULT_SUMMARY_FIELDS + ('image',), 'execution_environment': DEFAULT_SUMMARY_FIELDS + ('image',),
@@ -1903,6 +1904,10 @@ class HostSerializer(BaseSerializerWithVariables):
group_list = [{'id': g.id, 'name': g.name} for g in obj.groups.all().order_by('id')[:5]] group_list = [{'id': g.id, 'name': g.name} for g in obj.groups.all().order_by('id')[:5]]
group_cnt = obj.groups.count() group_cnt = obj.groups.count()
d.setdefault('groups', {'count': group_cnt, 'results': group_list}) d.setdefault('groups', {'count': group_cnt, 'results': group_list})
if obj.inventory.kind == 'constructed':
summaries_qs = obj.constructed_host_summaries
else:
summaries_qs = obj.job_host_summaries
d.setdefault( d.setdefault(
'recent_jobs', 'recent_jobs',
[ [
@@ -1913,7 +1918,7 @@ class HostSerializer(BaseSerializerWithVariables):
'status': j.job.status, 'status': j.job.status,
'finished': j.job.finished, 'finished': j.job.finished,
} }
for j in obj.job_host_summaries.select_related('job__job_template').order_by('-created').defer('job__extra_vars', 'job__artifacts')[:5] for j in summaries_qs.select_related('job__job_template').order_by('-created').defer('job__extra_vars', 'job__artifacts')[:5]
], ],
) )
return d return d
@@ -4140,6 +4145,7 @@ class JobHostSummarySerializer(BaseSerializer):
'-description', '-description',
'job', 'job',
'host', 'host',
'constructed_host',
'host_name', 'host_name',
'changed', 'changed',
'dark', 'dark',

View File

@@ -122,4 +122,17 @@ class Migration(migrations.Migration):
help_text='This field is deprecated and will be removed in a future release. Regex where only matching hosts will be imported.', help_text='This field is deprecated and will be removed in a future release. Regex where only matching hosts will be imported.',
), ),
), ),
migrations.AddField(
model_name='jobhostsummary',
name='constructed_host',
field=models.ForeignKey(
default=None,
editable=False,
help_text='Only for jobs run against constructed inventories, this links to the host inside the constructed inventory.',
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='constructed_host_summaries',
to='main.host',
),
),
] ]

View File

@@ -7,6 +7,7 @@ from collections import defaultdict
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models, DatabaseError from django.db import models, DatabaseError
from django.db.models.functions import Cast
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
from django.utils.text import Truncator from django.utils.text import Truncator
from django.utils.timezone import utc, now from django.utils.timezone import utc, now
@@ -538,23 +539,36 @@ class JobEvent(BasePlaybookEvent):
from awx.main.models import Host, JobHostSummary # circular import from awx.main.models import Host, JobHostSummary # circular import
all_hosts = Host.objects.filter(pk__in=self.host_map.values()).only('id', 'name') if self.job.inventory.kind == 'constructed':
all_hosts = Host.objects.filter(id__in=self.job.inventory.hosts.values_list(Cast('instance_id', output_field=models.IntegerField()))).only(
'id', 'name'
)
constructed_host_map = self.host_map
host_map = {host.name: host.id for host in all_hosts}
else:
all_hosts = Host.objects.filter(pk__in=self.host_map.values()).only('id', 'name')
constructed_host_map = {}
host_map = self.host_map
existing_host_ids = set(h.id for h in all_hosts) existing_host_ids = set(h.id for h in all_hosts)
summaries = dict() summaries = dict()
updated_hosts_list = list() updated_hosts_list = list()
for host in hostnames: for host in hostnames:
updated_hosts_list.append(host.lower()) updated_hosts_list.append(host.lower())
host_id = self.host_map.get(host, None) host_id = host_map.get(host)
if host_id not in existing_host_ids: if host_id not in existing_host_ids:
host_id = None host_id = None
constructed_host_id = constructed_host_map.get(host)
host_stats = {} host_stats = {}
for stat in ('changed', 'dark', 'failures', 'ignored', 'ok', 'processed', 'rescued', 'skipped'): for stat in ('changed', 'dark', 'failures', 'ignored', 'ok', 'processed', 'rescued', 'skipped'):
try: try:
host_stats[stat] = self.event_data.get(stat, {}).get(host, 0) host_stats[stat] = self.event_data.get(stat, {}).get(host, 0)
except AttributeError: # in case event_data[stat] isn't a dict. except AttributeError: # in case event_data[stat] isn't a dict.
pass pass
summary = JobHostSummary(created=now(), modified=now(), job_id=job.id, host_id=host_id, host_name=host, **host_stats) summary = JobHostSummary(
created=now(), modified=now(), job_id=job.id, host_id=host_id, constructed_host_id=constructed_host_id, host_name=host, **host_stats
)
summary.failed = bool(summary.dark or summary.failures) summary.failed = bool(summary.dark or summary.failures)
summaries[(host_id, host)] = summary summaries[(host_id, host)] = summary

View File

@@ -569,12 +569,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
default=None, default=None,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
hosts = models.ManyToManyField( hosts = models.ManyToManyField('Host', related_name='jobs', editable=False, through='JobHostSummary', through_fields=('job', 'host'))
'Host',
related_name='jobs',
editable=False,
through='JobHostSummary',
)
artifacts = JSONBlob( artifacts = JSONBlob(
default=dict, default=dict,
blank=True, blank=True,
@@ -1059,6 +1054,15 @@ class JobHostSummary(CreatedModifiedModel):
editable=False, editable=False,
) )
host = models.ForeignKey('Host', related_name='job_host_summaries', null=True, default=None, on_delete=models.SET_NULL, editable=False) host = models.ForeignKey('Host', related_name='job_host_summaries', null=True, default=None, on_delete=models.SET_NULL, editable=False)
constructed_host = models.ForeignKey(
'Host',
related_name='constructed_host_summaries',
null=True,
default=None,
on_delete=models.SET_NULL,
editable=False,
help_text='Only for jobs run against constructed inventories, this links to the host inside the constructed inventory.',
)
host_name = models.CharField( host_name = models.CharField(
max_length=1024, max_length=1024,