mirror of
https://github.com/ansible/awx.git
synced 2026-01-09 23:12:08 -03:30
Added separate method to start Job independently from creating it; Jobs no longer start automatically when first saved. Added method on JobTemplate to create a new Job with defaults copied from the template.
This commit is contained in:
parent
bc1f3e320e
commit
5901acb6a8
@ -24,6 +24,7 @@ from django.contrib.admin.util import unquote
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.html import format_html
|
||||
from lib.main.models import *
|
||||
@ -195,7 +196,15 @@ class VariableDataAdmin(BaseModelAdmin):
|
||||
|
||||
class CredentialAdmin(BaseModelAdmin):
|
||||
|
||||
list_display = ('name', 'description', 'active')
|
||||
fieldsets = (
|
||||
(None, {'fields': (('name', 'active'), ('user', 'team'), 'description')}),
|
||||
(_('Auth Info'), {'fields': ('default_username', 'ssh_key_data',
|
||||
'ssh_key_unlock', 'ssh_password',
|
||||
'sudo_password')}),
|
||||
#(_('Tags'), {'fields': ('tags',)}),
|
||||
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', 'audit_trail',)}),
|
||||
)
|
||||
readonly_fields = ('creation_date', 'created_by', 'audit_trail')
|
||||
filter_horizontal = ('tags',)
|
||||
|
||||
class TeamAdmin(BaseModelAdmin):
|
||||
@ -246,12 +255,16 @@ class JobTemplateAdmin(BaseModelAdmin):
|
||||
#filter_horizontal = ('tags',)
|
||||
|
||||
def get_create_link_display(self, obj):
|
||||
if not obj or not obj.pk:
|
||||
return ''
|
||||
info = Job._meta.app_label, Job._meta.module_name
|
||||
create_url = reverse('admin:%s_%s_add' % info,
|
||||
current_app=self.admin_site.name)
|
||||
create_opts = {
|
||||
'job_template': obj.pk,
|
||||
'job_type': obj.job_type,
|
||||
'description': obj.description,
|
||||
'name': '%s %s' % (obj.name, now().isoformat()),
|
||||
}
|
||||
if obj.inventory:
|
||||
create_opts['inventory'] = obj.inventory.pk
|
||||
@ -267,6 +280,8 @@ class JobTemplateAdmin(BaseModelAdmin):
|
||||
get_create_link_display.allow_tags = True
|
||||
|
||||
def get_jobs_link_display(self, obj):
|
||||
if not obj or not obj.pk:
|
||||
return ''
|
||||
info = Job._meta.app_label, Job._meta.module_name
|
||||
jobs_url = reverse('admin:%s_%s_changelist' % info,
|
||||
current_app=self.admin_site.name)
|
||||
@ -293,7 +308,8 @@ class JobAdmin(BaseModelAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('name', 'job_template', 'description')}),
|
||||
(_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
|
||||
'credential', 'job_type')}),
|
||||
'credential', 'job_type',
|
||||
'start_job')}),
|
||||
#(_('Tags'), {'fields': ('tags',)}),
|
||||
(_('Audit Trail'), {'fields': ('creation_date', 'created_by',
|
||||
'audit_trail',)}),
|
||||
@ -312,7 +328,7 @@ class JobAdmin(BaseModelAdmin):
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
ro_fields = list(super(JobAdmin, self).get_readonly_fields(request, obj))
|
||||
if obj and obj.pk:
|
||||
if obj and obj.pk and obj.status != 'new':
|
||||
ro_fields.extend(['name', 'description', 'job_template',
|
||||
'inventory', 'project', 'playbook', 'credential',
|
||||
'job_type'])
|
||||
@ -320,14 +336,21 @@ class JobAdmin(BaseModelAdmin):
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fsets = list(super(JobAdmin, self).get_fieldsets(request, obj))
|
||||
if not obj or not obj.pk:
|
||||
if not obj or not obj.pk or obj.status == 'new':
|
||||
fsets = [fs for fs in fsets if
|
||||
'creation_date' not in fs[1]['fields'] and
|
||||
'status' not in fs[1]['fields']]
|
||||
elif obj and obj.pk and obj.status != 'new':
|
||||
#print obj, obj.pk, obj.status
|
||||
for fs in fsets:
|
||||
# FIXME: Show start job on add view
|
||||
if 'start_job' in fs[1]['fields']:
|
||||
fs[1]['fields'] = [x for x in fs[1]['fields']
|
||||
if x != 'start_job']
|
||||
return fsets
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
if obj and obj.pk:
|
||||
if obj and obj.pk and obj.status != 'new':
|
||||
return super(JobAdmin, self).get_inline_instances(request, obj)
|
||||
else:
|
||||
return []
|
||||
|
||||
@ -71,8 +71,7 @@ class GroupForm(forms.ModelForm):
|
||||
class JobTemplateAdminForm(forms.ModelForm):
|
||||
'''Custom admin form for creating/editing JobTemplates.'''
|
||||
|
||||
playbook = forms.ChoiceField(choices=[EMPTY_CHOICE], required=False,
|
||||
widget=PlaybookSelect)
|
||||
playbook = forms.ChoiceField(choices=[EMPTY_CHOICE], widget=PlaybookSelect)
|
||||
|
||||
class Meta:
|
||||
model = JobTemplate
|
||||
@ -89,9 +88,30 @@ class JobTemplateAdminForm(forms.ModelForm):
|
||||
class JobAdminForm(JobTemplateAdminForm):
|
||||
'''Custom admin form for creating Jobs.'''
|
||||
|
||||
start_job = forms.BooleanField(initial=False, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JobAdminForm, self).__init__(*args, **kwargs)
|
||||
self.fields['playbook'].required = True
|
||||
if self.instance.pk and self.instance.status != 'new':
|
||||
self.fields.pop('playbook', None)
|
||||
|
||||
def clean_start_job(self):
|
||||
return self.cleaned_data.get('start_job', False)
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super(JobAdminForm, self).save(commit)
|
||||
save_m2m = getattr(self, 'save_m2m', lambda: None)
|
||||
should_start = bool(self.cleaned_data.get('start_job', '') and
|
||||
instance.status == 'new')
|
||||
def new_save_m2m():
|
||||
save_m2m()
|
||||
if should_start:
|
||||
instance.start()
|
||||
if commit:
|
||||
new_save_m2m()
|
||||
else:
|
||||
self.save_m2m = new_save_m2m
|
||||
return instance
|
||||
|
||||
@ -775,11 +775,19 @@ class JobTemplate(CommonModel):
|
||||
inventory = models.ForeignKey(
|
||||
'Inventory',
|
||||
related_name='job_templates',
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
project = models.ForeignKey(
|
||||
'Project',
|
||||
related_name='job_templates',
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
playbook = models.CharField(
|
||||
max_length=1024,
|
||||
default='',
|
||||
)
|
||||
credential = models.ForeignKey(
|
||||
'Credential',
|
||||
related_name='job_templates',
|
||||
@ -788,19 +796,27 @@ class JobTemplate(CommonModel):
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
project = models.ForeignKey(
|
||||
'Project',
|
||||
related_name='job_templates',
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
playbook = models.CharField(
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
default='',
|
||||
)
|
||||
|
||||
def create_job(self, **kwargs):
|
||||
'''
|
||||
Create a new job based on this template.
|
||||
'''
|
||||
start_job = kwargs.pop('start', False)
|
||||
save_job = kwargs.pop('save', True) or start_job # Start implies save.
|
||||
kwargs['job_template'] = self
|
||||
kwargs.setdefault('name', '%s %s' % (self.name, now().isoformat()))
|
||||
kwargs.setdefault('description', self.description)
|
||||
kwargs.setdefault('job_type', self.job_type)
|
||||
kwargs.setdefault('inventory', self.inventory)
|
||||
kwargs.setdefault('project', self.project)
|
||||
kwargs.setdefault('playbook', self.playbook)
|
||||
kwargs.setdefault('credential', self.credential)
|
||||
job = Job(**kwargs)
|
||||
if save_job:
|
||||
job.save()
|
||||
if start_job:
|
||||
job.start()
|
||||
return job
|
||||
|
||||
# project has one default playbook but really should have a list of playbooks and flags ...
|
||||
# ssh-agent bash
|
||||
@ -903,11 +919,12 @@ class Job(CommonModel):
|
||||
'''
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('pending', _('Pending')),
|
||||
('running', _('Running')),
|
||||
('successful', _('Successful')),
|
||||
('failed', _('Failed')),
|
||||
('error', _('Error')),
|
||||
('new', _('New')), # Job has been created, but not started.
|
||||
('pending', _('Pending')), # Job has been queued, but is not yet running.
|
||||
('running', _('Running')), # Job is currently running.
|
||||
('successful', _('Successful')), # Job completed successfully.
|
||||
('failed', _('Failed')), # Job completed, but with failures.
|
||||
('error', _('Error')), # The job was unable to run.
|
||||
]
|
||||
|
||||
class Meta:
|
||||
@ -949,7 +966,7 @@ class Job(CommonModel):
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='pending',
|
||||
default='new',
|
||||
editable=False,
|
||||
)
|
||||
result_stdout = models.TextField(
|
||||
@ -989,27 +1006,18 @@ class Job(CommonModel):
|
||||
except TaskMeta.DoesNotExist:
|
||||
pass
|
||||
|
||||
def _run(self):
|
||||
def start(self):
|
||||
from lib.main.tasks import run_job
|
||||
if self.status != 'new':
|
||||
return
|
||||
self.status = 'pending'
|
||||
self.save(update_fields=['status'])
|
||||
task_result = run_job.delay(self.pk)
|
||||
# The TaskMeta instance in the database isn't created until the worker
|
||||
# starts processing the task, so we can only store the task ID here.
|
||||
self.celery_task_id = task_result.task_id
|
||||
self.save(update_fields=['celery_task_id'])
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
created = not bool(self.pk)
|
||||
super(Job, self).save(*args, **kwargs)
|
||||
# Create a new host summary for each host in the inventory.
|
||||
for host in self.inventory.hosts.all():
|
||||
# Due to the way the inventory script is called, hosts without a
|
||||
# group won't be included.
|
||||
if host.groups.count():
|
||||
self.job_host_summaries.get_or_create(host=host)
|
||||
# Start job running (but only if just created).
|
||||
if created and self.status == 'pending':
|
||||
self._run()
|
||||
|
||||
@property
|
||||
def successful_hosts(self):
|
||||
return Host.objects.filter(job_host_summaries__job__pk=self.pk,
|
||||
|
||||
@ -285,7 +285,9 @@ class AcomCallbackEventTest(BaseCommandTest):
|
||||
|
||||
def test_with_job_status_not_running(self):
|
||||
# Events can only be added when the job is running.
|
||||
self.assertEqual(self.job.status, 'pending')
|
||||
self.assertEqual(self.job.status, 'new')
|
||||
self.job.status = 'pending'
|
||||
self.job.save()
|
||||
result, stdout, stderr = self.run_command('acom_callback_event',
|
||||
**self.valid_kwargs)
|
||||
self.assertTrue(isinstance(result, CommandError))
|
||||
|
||||
@ -80,20 +80,21 @@ class RunJobTest(BaseCeleryTest):
|
||||
|
||||
def create_test_job(self, **kwargs):
|
||||
opts = {
|
||||
'name': 'test-job',
|
||||
'name': 'test-job-template',
|
||||
'inventory': self.inventory,
|
||||
'project': self.project,
|
||||
'playbook': self.project.available_playbooks[0],
|
||||
}
|
||||
opts.update(kwargs)
|
||||
return Job.objects.create(**opts)
|
||||
self.job_template = JobTemplate.objects.create(**opts)
|
||||
return self.job_template.create_job()
|
||||
|
||||
def test_run_job(self):
|
||||
self.create_test_project(TEST_PLAYBOOK)
|
||||
job = self.create_test_job()
|
||||
self.assertEqual(job.status, 'new')
|
||||
job.start()
|
||||
self.assertEqual(job.status, 'pending')
|
||||
self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
|
||||
set([self.host.pk]))
|
||||
job = Job.objects.get(pk=job.pk)
|
||||
#print 'stdout:', job.result_stdout
|
||||
#print 'stderr:', job.result_stderr
|
||||
@ -102,8 +103,6 @@ class RunJobTest(BaseCeleryTest):
|
||||
self.assertEqual(job.status, 'successful')
|
||||
self.assertTrue(job.result_stdout)
|
||||
job_events = job.job_events.all()
|
||||
#for ev in launch_job_status_events:
|
||||
# print ev.event, ev.event_data
|
||||
self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1)
|
||||
self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1)
|
||||
self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2)
|
||||
@ -111,7 +110,6 @@ class RunJobTest(BaseCeleryTest):
|
||||
for evt in job_events.filter(event='runner_on_ok'):
|
||||
self.assertEqual(evt.host, self.host)
|
||||
self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1)
|
||||
#print job_events.get(event='playbook_on_stats').event_data
|
||||
self.assertEqual(job.successful_hosts.count(), 1)
|
||||
self.assertEqual(job.failed_hosts.count(), 0)
|
||||
self.assertEqual(job.changed_hosts.count(), 1)
|
||||
@ -122,9 +120,9 @@ class RunJobTest(BaseCeleryTest):
|
||||
def test_check_job(self):
|
||||
self.create_test_project(TEST_PLAYBOOK)
|
||||
job = self.create_test_job(job_type='check')
|
||||
self.assertEqual(job.status, 'new')
|
||||
job.start()
|
||||
self.assertEqual(job.status, 'pending')
|
||||
self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
|
||||
set([self.host.pk]))
|
||||
job = Job.objects.get(pk=job.pk)
|
||||
self.assertEqual(job.status, 'successful')
|
||||
self.assertTrue(job.result_stdout)
|
||||
@ -146,9 +144,9 @@ class RunJobTest(BaseCeleryTest):
|
||||
def test_run_job_that_fails(self):
|
||||
self.create_test_project(TEST_PLAYBOOK2)
|
||||
job = self.create_test_job()
|
||||
self.assertEqual(job.status, 'new')
|
||||
job.start()
|
||||
self.assertEqual(job.status, 'pending')
|
||||
self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
|
||||
set([self.host.pk]))
|
||||
job = Job.objects.get(pk=job.pk)
|
||||
self.assertEqual(job.status, 'failed')
|
||||
self.assertTrue(job.result_stdout)
|
||||
@ -169,9 +167,9 @@ class RunJobTest(BaseCeleryTest):
|
||||
def test_check_job_where_task_would_fail(self):
|
||||
self.create_test_project(TEST_PLAYBOOK2)
|
||||
job = self.create_test_job(job_type='check')
|
||||
self.assertEqual(job.status, 'new')
|
||||
job.start()
|
||||
self.assertEqual(job.status, 'pending')
|
||||
self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
|
||||
set([self.host.pk]))
|
||||
job = Job.objects.get(pk=job.pk)
|
||||
# Since we don't actually run the task, the --check should indicate
|
||||
# everything is successful.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user