diff --git a/ascender/main/management/commands/inventory_import.py b/ascender/main/management/commands/inventory_import.py index 455134e20..10586681f 100644 --- a/ascender/main/management/commands/inventory_import.py +++ b/ascender/main/management/commands/inventory_import.py @@ -797,7 +797,9 @@ def _relink_orphaned_job_host_summaries(self): for offset in range(0, len(host_names), self._batch_size): batch_names = host_names[offset : offset + self._batch_size] orphaned = JobHostSummary.objects.filter(job__inventory=self.inventory, host_name__in=batch_names, **null_filter) - for host_name in batch_names: + # Only issue an UPDATE for names that actually have orphans + names_with_orphans = set(orphaned.order_by().values_list('host_name', flat=True).distinct()) + for host_name in names_with_orphans: host_pk = host_map.get(host_name) if host_pk: updated = orphaned.filter(host_name=host_name).update(**{fk_field: host_pk}) diff --git a/ascender/main/tests/functional/commands/test_inventory_import.py b/ascender/main/tests/functional/commands/test_inventory_import.py index 7478a0a04..705f43973 100644 --- a/ascender/main/tests/functional/commands/test_inventory_import.py +++ b/ascender/main/tests/functional/commands/test_inventory_import.py @@ -591,3 +591,42 @@ def test_relink_does_not_cross_inventories(self, organization): summary = JobHostSummary.objects.get(job=job_b, host_name='server1') assert summary.host_id is None, "Summary from inv_b should not be re-linked to inv_a's host" + + def test_relink_only_updates_hosts_with_orphans(self, inventory): + from django.db import connection + from django.test.utils import CaptureQueriesContext + from ascender.main.models import JobHostSummary, Job, Project, JobTemplate + + project = Project.objects.create(name='test-proj') + jt = JobTemplate.objects.create(name='test-jt', inventory=inventory, project=project) + hosts = [Host.objects.create(name=f'server{i}', inventory=inventory) for i in range(5)] + job = Job.objects.create(inventory=inventory, job_template=jt, status='successful') + JobHostSummary.objects.create(job=job, host=None, host_name='server3', ok=1) + + cmd = inventory_import.Command() + cmd.inventory = inventory + cmd._batch_size = 2 + with CaptureQueriesContext(connection) as ctx: + cmd._relink_orphaned_job_host_summaries() + + updates = [q['sql'] for q in ctx.captured_queries if q['sql'].startswith('UPDATE "main_jobhostsummary"')] + assert len(updates) == 1 + # One lookup per batch of names: 5 hosts in batches of 2 + selects = [q['sql'] for q in ctx.captured_queries if q['sql'].startswith('SELECT DISTINCT "main_jobhostsummary"."host_name"')] + assert len(selects) == 3 + assert JobHostSummary.objects.get(job=job, host_name='server3').host_id == hosts[3].pk + + def test_relink_issues_no_updates_without_orphans(self, inventory): + from django.db import connection + from django.test.utils import CaptureQueriesContext + + for i in range(5): + Host.objects.create(name=f'server{i}', inventory=inventory) + + cmd = inventory_import.Command() + cmd.inventory = inventory + cmd._batch_size = 2 + with CaptureQueriesContext(connection) as ctx: + cmd._relink_orphaned_job_host_summaries() + + assert not [q for q in ctx.captured_queries if q['sql'].startswith('UPDATE')]