refactor notifications

This commit is contained in:
Chris Meyers 2016-08-30 14:36:18 -04:00
parent 116e40dbe7
commit 27b9fb8dab
7 changed files with 146 additions and 97 deletions

View File

@ -23,13 +23,14 @@ from awx.main.models.base import * # noqa
from awx.main.models.unified_jobs import * # noqa
from awx.main.utils import decrypt_field
from awx.main.conf import tower_settings
from awx.main.models.notifications import JobNotificationMixin
logger = logging.getLogger('awx.main.models.ad_hoc_commands')
__all__ = ['AdHocCommand', 'AdHocCommandEvent']
class AdHocCommand(UnifiedJob):
class AdHocCommand(UnifiedJob, JobNotificationMixin):
class Meta(object):
app_label = 'main'
@ -237,6 +238,14 @@ class AdHocCommand(UnifiedJob):
update_fields.append('name')
super(AdHocCommand, self).save(*args, **kwargs)
'''
JobNotificationMixin
'''
def get_notification_templates(self):
return self.notification_templates
def get_notification_friendly_name(self):
return "AdHoc Command"
class AdHocCommandEvent(CreatedModifiedModel):
'''

View File

@ -25,7 +25,10 @@ from awx.main.models.base import * # noqa
from awx.main.models.jobs import Job
from awx.main.models.unified_jobs import * # noqa
from awx.main.models.mixins import ResourceMixin
from awx.main.models.notifications import NotificationTemplate
from awx.main.models.notifications import (
NotificationTemplate,
JobNotificationMixin,
)
from awx.main.utils import _inventory_updates
from awx.main.conf import tower_settings
@ -1192,7 +1195,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
return source
class InventoryUpdate(UnifiedJob, InventorySourceOptions):
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
'''
Internal job for tracking inventory updates from external sources.
'''
@ -1268,6 +1271,15 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions):
return False
return True
'''
JobNotificationMixin
'''
def get_notification_templates(self):
return self.inventory_source.notification_templates
def get_notification_friendly_name(self):
return "Inventory Update"
class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):

View File

@ -24,7 +24,10 @@ from jsonfield import JSONField
from awx.main.constants import CLOUD_PROVIDERS
from awx.main.models.base import * # noqa
from awx.main.models.unified_jobs import * # noqa
from awx.main.models.notifications import NotificationTemplate
from awx.main.models.notifications import (
NotificationTemplate,
JobNotificationMixin,
)
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
from awx.main.utils import emit_websocket_notification
from awx.main.redact import PlainTextCleaner
@ -499,7 +502,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
any_notification_templates = set(any_notification_templates + list(base_notification_templates.filter(organization_notification_templates_for_any=self.project.organization)))
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
class Job(UnifiedJob, JobOptions):
class Job(UnifiedJob, JobOptions, JobNotificationMixin):
'''
A job applies a project (with playbook) to an inventory source with a given
credential. It represents a single invocation of ansible-playbook with the
@ -792,6 +795,15 @@ class Job(UnifiedJob, JobOptions):
return True
'''
JobNotificationMixin
'''
def get_notification_templates(self):
return self.job_template.notification_templates
def get_notification_friendly_name(self):
return "Job"
class JobHostSummary(CreatedModifiedModel):
'''
Per-host statistics for each job.
@ -1315,7 +1327,7 @@ class SystemJobTemplate(UnifiedJobTemplate, SystemJobOptions):
any=list(any_notification_templates))
class SystemJob(UnifiedJob, SystemJobOptions):
class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin):
class Meta:
app_label = 'main'
@ -1378,3 +1390,13 @@ class SystemJob(UnifiedJob, SystemJobOptions):
@property
def task_impact(self):
return 150
'''
JobNotificationMixin
'''
def get_notification_templates(self):
return self.system_job_template.notification_templates
def get_notification_friendly_name(self):
return "System Job"

View File

@ -171,3 +171,27 @@ class Notification(CreatedModifiedModel):
def get_absolute_url(self):
return reverse('api:notification_detail', args=(self.pk,))
class JobNotificationMixin(object):
def get_notification_templates(self):
raise RuntimeError("Define me")
def get_notification_friendly_name(self):
raise RuntimeError("Define me")
def _build_notification_message(self, status_str):
notification_body = self.notification_data()
notification_subject = "{} #{} '{}' {} on Ansible Tower: {}".format(self.get_notification_friendly_name(),
self.id,
self.name,
status_str,
notification_body['url'])
notification_body['friendly_name'] = self.get_notification_friendly_name()
return (notification_subject, notification_body)
def build_notification_succeeded_message(self):
return self._build_notification_message('succeeded')
def build_notification_failed_message(self):
return self._build_notification_message('failed')

View File

@ -20,7 +20,10 @@ from django.utils.timezone import now, make_aware, get_default_timezone
from awx.lib.compat import slugify
from awx.main.models.base import * # noqa
from awx.main.models.jobs import Job
from awx.main.models.notifications import NotificationTemplate
from awx.main.models.notifications import (
NotificationTemplate,
JobNotificationMixin,
)
from awx.main.models.unified_jobs import * # noqa
from awx.main.models.mixins import ResourceMixin
from awx.main.utils import update_scm_url
@ -372,8 +375,7 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
def get_absolute_url(self):
return reverse('api:project_detail', args=(self.pk,))
class ProjectUpdate(UnifiedJob, ProjectOptions):
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
'''
Internal job for tracking project updates from SCM.
'''
@ -443,3 +445,12 @@ class ProjectUpdate(UnifiedJob, ProjectOptions):
if 'scm_delete_on_next_update' not in update_fields:
update_fields.append('scm_delete_on_next_update')
parent_instance.save(update_fields=update_fields)
'''
JobNotificationMixin
'''
def get_notification_templates(self):
return self.project.notification_templates
def get_notification_friendly_name(self):
return "Project Update"

View File

@ -18,6 +18,7 @@ from django.core.exceptions import NON_FIELD_ERRORS
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now
from django.utils.encoding import smart_text
from django.apps import apps
# Django-JSONField
from jsonfield import JSONField
@ -360,8 +361,30 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
return unified_job
class UnifiedJobTypeStringMixin(object):
@classmethod
def _underscore_to_camel(cls, word):
return ''.join(x.capitalize() or '_' for x in word.split('_'))
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique):
@classmethod
def _model_type(cls, job_type):
# Django >= 1.9
#app = apps.get_app_config('main')
model_str = cls._underscore_to_camel(job_type)
try:
return apps.get_model('main', model_str)
except LookupError:
print("Lookup model error")
return None
@classmethod
def get_instance_by_type(cls, job_type, job_id):
model = cls._model_type(job_type)
if not model:
return None
return model.objects.get(id=job_id)
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin):
'''
Concrete base class for unified job run by the task engine.
'''

View File

@ -185,114 +185,61 @@ def notify_task_runner(metadata_dict):
queue = FifoQueue('tower_task_manager')
queue.push(metadata_dict)
def _send_notification_templates(instance, status_str):
if status_str not in ['succeeded', 'failed']:
raise ValueError("status_str must be either succeeded or failed")
print("Instance has some shit in it %s" % instance)
notification_templates = instance.get_notification_templates()
if notification_templates:
all_notification_templates = set(notification_templates.get('success', []) + notification_templates.get('any', []))
if len(all_notification_templates):
try:
(notification_subject, notification_body) = getattr(instance, 'build_notification_%s_message' % status_str)()
except AttributeError:
raise NotImplementedError("build_notification_%s_message() does not exist" % status_str)
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in all_notification_templates],
job_id=instance.id)
@task(bind=True)
def handle_work_success(self, result, task_actual):
if task_actual['type'] == 'project_update':
instance = ProjectUpdate.objects.get(id=task_actual['id'])
instance_name = instance.name
notification_templates = instance.project.notification_templates
friendly_name = "Project Update"
elif task_actual['type'] == 'inventory_update':
instance = InventoryUpdate.objects.get(id=task_actual['id'])
instance_name = instance.name
notification_templates = instance.inventory_source.notification_templates
friendly_name = "Inventory Update"
elif task_actual['type'] == 'job':
instance = Job.objects.get(id=task_actual['id'])
instance_name = instance.job_template.name
notification_templates = instance.job_template.notification_templates
friendly_name = "Job"
elif task_actual['type'] == 'ad_hoc_command':
instance = AdHocCommand.objects.get(id=task_actual['id'])
instance_name = instance.module_name
notification_templates = instance.notification_templates
friendly_name = "AdHoc Command"
elif task_actual['type'] == 'system_job':
instance = SystemJob.objects.get(id=task_actual['id'])
instance_name = instance.system_job_template.name
notification_templates = instance.system_job_template.notification_templates
friendly_name = "System Job"
else:
instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id'])
if not instance:
return
all_notification_templates = set(notification_templates.get('success', []) + notification_templates.get('any', []))
if len(all_notification_templates):
notification_body = instance.notification_data()
notification_subject = "{} #{} '{}' succeeded on Ansible Tower: {}".format(friendly_name,
task_actual['id'],
smart_str(instance_name),
notification_body['url'])
notification_body['friendly_name'] = friendly_name
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in all_notification_templates],
job_id=task_actual['id'])
_send_notification_templates(instance, 'succeeded')
@task(bind=True)
def handle_work_error(self, task_id, subtasks=None):
print('Executing error task id %s, subtasks: %s' %
(str(self.request.id), str(subtasks)))
first_task = None
first_task_id = None
first_task_type = ''
first_task_name = ''
first_instance = None
first_instance_type = ''
if subtasks is not None:
for each_task in subtasks:
instance_name = ''
if each_task['type'] == 'project_update':
instance = ProjectUpdate.objects.get(id=each_task['id'])
instance_name = instance.name
notification_templates = instance.project.notification_templates
friendly_name = "Project Update"
elif each_task['type'] == 'inventory_update':
instance = InventoryUpdate.objects.get(id=each_task['id'])
instance_name = instance.name
notification_templates = instance.inventory_source.notification_templates
friendly_name = "Inventory Update"
elif each_task['type'] == 'job':
instance = Job.objects.get(id=each_task['id'])
instance_name = instance.job_template.name
notification_templates = instance.job_template.notification_templates
friendly_name = "Job"
elif each_task['type'] == 'ad_hoc_command':
instance = AdHocCommand.objects.get(id=each_task['id'])
instance_name = instance.module_name
notification_templates = instance.notification_templates
friendly_name = "AdHoc Command"
elif each_task['type'] == 'system_job':
instance = SystemJob.objects.get(id=each_task['id'])
instance_name = instance.system_job_template.name
notification_templates = instance.system_job_template.notification_templates
friendly_name = "System Job"
else:
instance = UnifiedJob.get_instance_by_type(each_task['type'], each_task['id'])
if not instance:
# Unknown task type
logger.warn("Unknown task type: {}".format(each_task['type']))
continue
if first_task is None:
first_task = instance
first_task_id = instance.id
first_task_type = each_task['type']
first_task_name = instance_name
first_task_friendly_name = friendly_name
if first_instance is None:
first_instance = instance
first_instance_type = each_task['type']
if instance.celery_task_id != task_id:
instance.status = 'failed'
instance.failed = True
instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \
(first_task_type, first_task_name, first_task_id)
(first_instance_type, first_instance.name, first_instance.id)
instance.save()
instance.socketio_emit_status("failed")
all_notification_templates = set(notification_templates.get('error', []) + notification_templates.get('any', []))
if len(all_notification_templates):
notification_body = first_task.notification_data()
notification_subject = "{} #{} '{}' failed on Ansible Tower: {}".format(first_task_friendly_name,
first_task_id,
smart_str(first_task_name),
notification_body['url'])
notification_body['friendly_name'] = first_task_friendly_name
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in all_notification_templates],
job_id=first_task_id)
if first_instance:
print("Instance type is %s" % first_instance_type)
print("Instance passing along %s" % first_instance.name)
_send_notification_templates(first_instance, 'failed')
@task()
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
@ -1710,3 +1657,4 @@ class RunSystemJob(BaseTask):
def build_cwd(self, instance, **kwargs):
return settings.BASE_DIR