1 6
import datetime
2 6
import logging
3

4 6
from django.db import models
5 6
from django.utils.dateparse import parse_datetime
6 6
from django.utils.timezone import utc
7 6
from django.utils.translation import ugettext_lazy as _
8

9 6
from cyborgbackup.api.versioning import reverse
10 6
from cyborgbackup.main.fields import JSONField
11 6
from cyborgbackup.main.models.base import CreatedModifiedModel
12

13 6
logger = logging.getLogger('cyborgbackup.analytics.job_events')
14

15 6
__all__ = ['JobEvent']
16

17

18 6
class JobEvent(CreatedModifiedModel):
19
    '''
20
    An event/message logged from the callback when running a job.
21
    '''
22

23 6
    EVENT_TYPES = [
24
        (0, 'debug', _('Debug'), False),
25
        (0, 'verbose', _('Verbose'), False),
26
        (0, 'deprecated', _('Deprecated'), False),
27
        (0, 'warning', _('Warning'), False),
28
        (0, 'system_warning', _('System Warning'), False),
29
        (0, 'error', _('Error'), True),
30
    ]
31

32 6
    FAILED_EVENTS = [x[1] for x in EVENT_TYPES if x[3]]
33 6
    EVENT_CHOICES = [(x[1], x[2]) for x in EVENT_TYPES]
34 6
    LEVEL_FOR_EVENT = dict([(x[1], x[0]) for x in EVENT_TYPES])
35

36 6
    VALID_KEYS = [
37
        'event', 'event_data', 'task', 'created', 'uuid', 'stdout',
38
        'start_line', 'end_line', 'verbosity', 'job_id', 'counter'
39
    ]
40

41 6
    class Meta:
42 6
        app_label = 'main'
43 6
        ordering = ('pk',)
44 6
        index_together = [
45
            ('job', 'event'),
46
            ('job', 'uuid'),
47
            ('job', 'start_line'),
48
            ('job', 'end_line'),
49
            ('job', 'parent_uuid'),
50
        ]
51

52 6
    uuid = models.CharField(
53
        max_length=1024,
54
        default='',
55
        editable=False,
56
    )
57 6
    parent_uuid = models.CharField(
58
        max_length=1024,
59
        default='',
60
        editable=False,
61
    )
62 6
    event = models.CharField(
63
        max_length=100,
64
        choices=EVENT_CHOICES,
65
    )
66 6
    event_data = JSONField(
67
        blank=True,
68
        default={},
69
    )
70 6
    failed = models.BooleanField(
71
        default=False,
72
        editable=False,
73
    )
74 6
    changed = models.BooleanField(
75
        default=False,
76
        editable=False,
77
    )
78 6
    task = models.CharField(
79
        max_length=1024,
80
        default='',
81
        editable=False,
82
    )
83 6
    counter = models.PositiveIntegerField(
84
        default=0,
85
        editable=False,
86
    )
87 6
    stdout = models.TextField(
88
        default='',
89
        editable=False,
90
    )
91 6
    verbosity = models.PositiveIntegerField(
92
        default=0,
93
        editable=False,
94
    )
95 6
    start_line = models.PositiveIntegerField(
96
        default=0,
97
        editable=False,
98
    )
99 6
    end_line = models.PositiveIntegerField(
100
        default=0,
101
        editable=False,
102
    )
103 6
    job = models.ForeignKey(
104
        'Job',
105
        related_name='job_events',
106
        on_delete=models.CASCADE,
107
        editable=False,
108
    )
109

110 6
    @property
111
    def event_level(self):
112 0
        return self.LEVEL_FOR_EVENT.get(self.event, 0)
113

114 6
    def get_event_display2(self):
115 0
        msg = self.get_event_display()
116

117
        # Change display for runner events triggered by async polling.  Some of
118
        # these events may not show in most cases, due to filterting them out
119
        # of the job event queryset returned to the user.
120 0
        return msg
121

122 6
    def _update_from_event_data(self):
123
        # Update event model fields from event data.
124 0
        updated_fields = set()
125 0
        event_data = self.event_data
126 0
        res = event_data.get('res', None)
127 0
        if self.event in self.FAILED_EVENTS and not event_data.get('ignore_errors', False):
128 0
            self.failed = True
129 0
            updated_fields.add('failed')
130 0
        if isinstance(res, dict):
131 0
            if res.get('changed', False):
132 0
                self.changed = True
133 0
                updated_fields.add('changed')
134
            # If we're not in verbose mode, wipe out any module arguments.
135 0
            invocation = res.get('invocation', None)
136 0
            if isinstance(invocation, dict) and self.job_verbosity == 0 and 'module_args' in invocation:
137 0
                event_data['res']['invocation']['module_args'] = ''
138 0
                self.event_data = event_data
139 0
                updated_fields.add('event_data')
140 0
        return updated_fields
141

142 6
    @classmethod
143
    def create_from_data(self, **kwargs):
144 0
        pk = None
145 0
        for key in ('job_id',):
146 0
            if key in kwargs:
147 0
                pk = key
148 0
        if pk is None:
149 0
            return
150

151
        # Convert the datetime for the job event's creation appropriately,
152
        # and include a time zone for it.
153
        #
154
        # In the event of any issue, throw it out, and Django will just save
155
        # the current time.
156 0
        try:
157 0
            if not isinstance(kwargs['created'], datetime.datetime):
158 0
                kwargs['created'] = parse_datetime(kwargs['created'])
159 0
            if not kwargs['created'].tzinfo:
160 0
                kwargs['created'] = kwargs['created'].replace(tzinfo=utc)
161 0
        except (KeyError, ValueError):
162 0
            kwargs.pop('created', None)
163

164
        # Sanity check: Don't honor keys that we don't recognize.
165 0
        for key in list(kwargs.keys()):
166 0
            if key not in self.VALID_KEYS:
167 0
                kwargs.pop(key)
168

169 0
        job_event = self.objects.create(**kwargs)
170 0
        logger.info('Event data saved.', extra=dict(python_objects=dict(job_event=job_event)))
171 0
        return job_event
172

173 6
    @property
174
    def job_verbosity(self):
175 0
        return self.job.verbosity
176

177 6
    def save(self, *args, **kwargs):
178
        # If update_fields has been specified, add our field names to it,
179
        # if it hasn't been specified, then we're just doing a normal save.
180 0
        update_fields = kwargs.get('update_fields', [])
181
        # Update model fields and related objects unless we're only updating
182
        # failed/changed flags triggered from a child event.
183 0
        from_parent_update = kwargs.pop('from_parent_update', False)
184 0
        if not from_parent_update:
185
            # Update model fields from event data.
186 0
            updated_fields = self._update_from_event_data()
187 0
            for field in updated_fields:
188 0
                if field not in update_fields:
189 0
                    update_fields.append(field)
190 0
        super(JobEvent, self).save(*args, **kwargs)
191

192 6
    def get_absolute_url(self, request=None):
193 0
        return reverse('api:job_event_detail', kwargs={'pk': self.pk}, request=request)
194

195 6
    def __unicode__(self):
196 0
        return u'%s @ %s' % (self.get_event_display2(), self.created.isoformat())

Read our documentation on viewing source code .

Loading