mirror of
https://github.com/ansible/awx.git
synced 2026-04-04 17:55:06 -02:30
Fix IntegrityError deleting job splitting JT
misc: *show sharded jobs in recent_jobs *test updates
This commit is contained in:
@@ -2976,12 +2976,9 @@ class JobTemplateMixin(object):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def _recent_jobs(self, obj):
|
def _recent_jobs(self, obj):
|
||||||
if hasattr(obj, 'workflow_jobs'):
|
job_mgr = obj.unifiedjob_unified_jobs.non_polymorphic().only('id', 'status', 'finished')
|
||||||
job_mgr = obj.workflow_jobs
|
|
||||||
else:
|
|
||||||
job_mgr = obj.jobs
|
|
||||||
return [{'id': x.id, 'status': x.status, 'finished': x.finished}
|
return [{'id': x.id, 'status': x.status, 'finished': x.finished}
|
||||||
for x in job_mgr.all().order_by('-created')[:10]]
|
for x in job_mgr.order_by('-created')[:10]]
|
||||||
|
|
||||||
def get_summary_fields(self, obj):
|
def get_summary_fields(self, obj):
|
||||||
d = super(JobTemplateMixin, self).get_summary_fields(obj)
|
d = super(JobTemplateMixin, self).get_summary_fields(obj)
|
||||||
|
|||||||
21
awx/main/migrations/0050_v340_unified_jt_set_null.py
Normal file
21
awx/main/migrations/0050_v340_unified_jt_set_null.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.11 on 2018-09-10 17:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import awx.main.utils.polymorphic
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0049_v340_add_job_template'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unifiedjob',
|
||||||
|
name='unified_job_template',
|
||||||
|
field=models.ForeignKey(default=None, editable=False, null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, related_name='unifiedjob_unified_jobs', to='main.UnifiedJobTemplate'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -320,10 +320,13 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
|
|||||||
def resources_needed_to_start(self):
|
def resources_needed_to_start(self):
|
||||||
return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))]
|
return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))]
|
||||||
|
|
||||||
def create_unified_job(self, **kwargs):
|
def create_job(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Create a new job based on this template.
|
Create a new job based on this template.
|
||||||
'''
|
'''
|
||||||
|
return self.create_unified_job(**kwargs)
|
||||||
|
|
||||||
|
def create_unified_job(self, **kwargs):
|
||||||
split_event = bool(
|
split_event = bool(
|
||||||
self.job_shard_count > 1 and
|
self.job_shard_count > 1 and
|
||||||
not kwargs.pop('_prevent_sharding', False)
|
not kwargs.pop('_prevent_sharding', False)
|
||||||
@@ -345,7 +348,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
|
|||||||
create_kwargs = dict(workflow_job=job,
|
create_kwargs = dict(workflow_job=job,
|
||||||
unified_job_template=self,
|
unified_job_template=self,
|
||||||
ancestor_artifacts=dict(job_shard=idx))
|
ancestor_artifacts=dict(job_shard=idx))
|
||||||
wfjn = WorkflowJobNode.objects.create(**create_kwargs)
|
WorkflowJobNode.objects.create(**create_kwargs)
|
||||||
return job
|
return job
|
||||||
|
|
||||||
def get_absolute_url(self, request=None):
|
def get_absolute_url(self, request=None):
|
||||||
@@ -480,7 +483,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
|
|||||||
RelatedJobsMixin
|
RelatedJobsMixin
|
||||||
'''
|
'''
|
||||||
def _get_related_jobs(self):
|
def _get_related_jobs(self):
|
||||||
return Job.objects.filter(job_template=self)
|
return UnifiedJob.objects.filter(unified_job_template=self)
|
||||||
|
|
||||||
|
|
||||||
class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskManagerJobMixin):
|
class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskManagerJobMixin):
|
||||||
|
|||||||
@@ -321,8 +321,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
|||||||
'''
|
'''
|
||||||
Create a new unified job based on this unified job template.
|
Create a new unified job based on this unified job template.
|
||||||
'''
|
'''
|
||||||
from awx.main.models import JobTemplate, WorkflowJob
|
|
||||||
|
|
||||||
new_job_passwords = kwargs.pop('survey_passwords', {})
|
new_job_passwords = kwargs.pop('survey_passwords', {})
|
||||||
eager_fields = kwargs.pop('_eager_fields', None)
|
eager_fields = kwargs.pop('_eager_fields', None)
|
||||||
|
|
||||||
@@ -553,7 +551,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
default=None,
|
default=None,
|
||||||
editable=False,
|
editable=False,
|
||||||
related_name='%(class)s_unified_jobs',
|
related_name='%(class)s_unified_jobs',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=polymorphic.SET_NULL,
|
||||||
)
|
)
|
||||||
launch_type = models.CharField(
|
launch_type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
@@ -834,7 +832,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
'''
|
'''
|
||||||
unified_job_class = self.__class__
|
unified_job_class = self.__class__
|
||||||
unified_jt_class = self._get_unified_job_template_class()
|
unified_jt_class = self._get_unified_job_template_class()
|
||||||
parent_field_name = unified_job_class._get_parent_field_name()
|
parent_field_name = self._get_parent_field_name()
|
||||||
fields = unified_jt_class._get_unified_job_field_names() | set([parent_field_name])
|
fields = unified_jt_class._get_unified_job_field_names() | set([parent_field_name])
|
||||||
|
|
||||||
create_data = {}
|
create_data = {}
|
||||||
@@ -881,6 +879,8 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
config = JobLaunchConfig(job=self)
|
config = JobLaunchConfig(job=self)
|
||||||
if parent is None:
|
if parent is None:
|
||||||
parent = getattr(self, self._get_parent_field_name())
|
parent = getattr(self, self._get_parent_field_name())
|
||||||
|
if parent is None:
|
||||||
|
return
|
||||||
valid_fields = parent.get_ask_mapping().keys()
|
valid_fields = parent.get_ask_mapping().keys()
|
||||||
# Special cases allowed for workflows
|
# Special cases allowed for workflows
|
||||||
if hasattr(self, 'extra_vars'):
|
if hasattr(self, 'extra_vars'):
|
||||||
|
|||||||
@@ -122,6 +122,22 @@ def test_job_relaunch_on_failed_hosts(post, inventory, project, machine_credenti
|
|||||||
assert r.data.get('limit') == hosts
|
assert r.data.get('limit') == hosts
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_shard_jt_recent_jobs(shard_job_factory, admin_user, get):
|
||||||
|
workflow_job = shard_job_factory(3, spawn=True)
|
||||||
|
shard_jt = workflow_job.job_template
|
||||||
|
r = get(
|
||||||
|
url=shard_jt.get_absolute_url(),
|
||||||
|
user=admin_user,
|
||||||
|
expect=200
|
||||||
|
)
|
||||||
|
job_ids = [entry['id'] for entry in r.data['summary_fields']['recent_jobs']]
|
||||||
|
assert workflow_job.pk in job_ids
|
||||||
|
for node in workflow_job.workflow_nodes.all():
|
||||||
|
job = node.job
|
||||||
|
assert job.pk in job_ids
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_block_unprocessed_events(delete, admin_user, mocker):
|
def test_block_unprocessed_events(delete, admin_user, mocker):
|
||||||
time_of_finish = parse("Thu Feb 28 09:10:20 2013 -0500")
|
time_of_finish = parse("Thu Feb 28 09:10:20 2013 -0500")
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ def test_job_host_summary_representation(host):
|
|||||||
host.delete()
|
host.delete()
|
||||||
assert 'N/A changed=1 dark=2 failures=3 ok=4 processed=5 skipped=6' == six.text_type(jhs)
|
assert 'N/A changed=1 dark=2 failures=3 ok=4 processed=5 skipped=6' == six.text_type(jhs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestShardingModels:
|
class TestShardingModels:
|
||||||
|
|
||||||
@@ -97,6 +98,6 @@ class TestShardingModels:
|
|||||||
job = shard_job_factory(3, jt_kwargs={'ask_limit_on_launch': True}, prompts={'limit': 'foobar'}, spawn=True)
|
job = shard_job_factory(3, jt_kwargs={'ask_limit_on_launch': True}, prompts={'limit': 'foobar'}, spawn=True)
|
||||||
assert job.launch_config.prompts_dict() == {'limit': 'foobar'}
|
assert job.launch_config.prompts_dict() == {'limit': 'foobar'}
|
||||||
for node in job.workflow_nodes.all():
|
for node in job.workflow_nodes.all():
|
||||||
assert node.limit == None # data not saved in node prompts
|
assert node.limit is None # data not saved in node prompts
|
||||||
job = node.job
|
job = node.job
|
||||||
assert job.limit == 'foobar'
|
assert job.limit == 'foobar'
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ class TestCreateUnifiedJob:
|
|||||||
job_with_links.save()
|
job_with_links.save()
|
||||||
job_with_links.credentials.add(machine_credential)
|
job_with_links.credentials.add(machine_credential)
|
||||||
job_with_links.credentials.add(net_credential)
|
job_with_links.credentials.add(net_credential)
|
||||||
with mocker.patch('awx.main.models.unified_jobs.UnifiedJobTemplate._get_unified_job_field_names',
|
second_job = job_with_links.copy_unified_job()
|
||||||
return_value=['inventory', 'credential', 'limit']):
|
|
||||||
second_job = job_with_links.copy_unified_job()
|
|
||||||
|
|
||||||
# Check that job data matches the original variables
|
# Check that job data matches the original variables
|
||||||
assert second_job.credential == job_with_links.credential
|
assert second_job.credential == job_with_links.credential
|
||||||
|
|||||||
@@ -71,14 +71,19 @@ class TestJobTemplateSerializerGetRelated():
|
|||||||
class TestJobTemplateSerializerGetSummaryFields():
|
class TestJobTemplateSerializerGetSummaryFields():
|
||||||
def test__recent_jobs(self, mocker, job_template, jobs):
|
def test__recent_jobs(self, mocker, job_template, jobs):
|
||||||
|
|
||||||
job_template.jobs.all = mocker.MagicMock(**{'order_by.return_value': jobs})
|
job_template.unifiedjob_unified_jobs = mocker.MagicMock(**{
|
||||||
job_template.jobs.all.return_value = job_template.jobs.all
|
'non_polymorphic.return_value': mocker.MagicMock(**{
|
||||||
|
'only.return_value': mocker.MagicMock(**{
|
||||||
|
'order_by.return_value': jobs
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
serializer = JobTemplateSerializer()
|
serializer = JobTemplateSerializer()
|
||||||
recent_jobs = serializer._recent_jobs(job_template)
|
recent_jobs = serializer._recent_jobs(job_template)
|
||||||
|
|
||||||
job_template.jobs.all.assert_called_once_with()
|
job_template.unifiedjob_unified_jobs.non_polymorphic.assert_called_once_with()
|
||||||
job_template.jobs.all.order_by.assert_called_once_with('-created')
|
job_template.unifiedjob_unified_jobs.non_polymorphic().only().order_by.assert_called_once_with('-created')
|
||||||
assert len(recent_jobs) == 10
|
assert len(recent_jobs) == 10
|
||||||
for x in jobs[:10]:
|
for x in jobs[:10]:
|
||||||
assert recent_jobs == [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in jobs[:10]]
|
assert recent_jobs == [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in jobs[:10]]
|
||||||
|
|||||||
Reference in New Issue
Block a user