review updates

This commit is contained in:
John Anderson 2020-07-06 01:58:28 -04:00
parent f092c107b5
commit 41f92ef8e6
11 changed files with 130 additions and 75 deletions

View File

@ -120,6 +120,43 @@ class TemplateLanguageChoices(ChoiceSet):
} }
#
# Log Levels for Reports and Scripts
#
class LogLevelChoices(ChoiceSet):
LOG_DEFAULT = 'default'
LOG_SUCCESS = 'sucess'
LOG_INFO = 'info'
LOG_WARNING = 'warning'
LOG_FAILURE = 'failure'
CHOICES = (
(LOG_DEFAULT, 'Default'),
(LOG_SUCCESS, 'Success'),
(LOG_INFO, 'Info'),
(LOG_WARNING, 'Warning'),
(LOG_FAILURE, 'Failure'),
)
CLASS_MAP = (
(LOG_DEFAULT, 'default'),
(LOG_SUCCESS, 'success'),
(LOG_INFO, 'info'),
(LOG_WARNING, 'warning'),
(LOG_FAILURE, 'danger'),
)
LEGACY_MAP = (
(LOG_DEFAULT, 0),
(LOG_SUCCESS, 10),
(LOG_INFO, 20),
(LOG_WARNING, 30),
(LOG_FAILURE, 40),
)
# #
# Job results # Job results
# #

View File

@ -1,17 +1,3 @@
# Report logging levels
LOG_DEFAULT = 0
LOG_SUCCESS = 10
LOG_INFO = 20
LOG_WARNING = 30
LOG_FAILURE = 40
LOG_LEVEL_CODES = {
LOG_DEFAULT: 'default',
LOG_SUCCESS: 'success',
LOG_INFO: 'info',
LOG_WARNING: 'warning',
LOG_FAILURE: 'failure',
}
# Webhook content types # Webhook content types
HTTP_CONTENT_TYPE_JSON = 'application/json' HTTP_CONTENT_TYPE_JSON = 'application/json'

View File

@ -9,8 +9,7 @@ from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django_rq import job from django_rq import job
from .choices import JobResultStatusChoices from .choices import JobResultStatusChoices, LogLevelChoices
from .constants import *
from .models import JobResult from .models import JobResult
@ -77,7 +76,8 @@ def run_report(job_result, *args, **kwargs):
try: try:
report.run(job_result) report.run(job_result)
except Exception: except Exception as e:
print(e)
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
logging.error(f"Error during execution of report {job_result.name}") logging.error(f"Error during execution of report {job_result.name}")
@ -153,15 +153,15 @@ class Report(object):
def full_name(self): def full_name(self):
return '.'.join([self.__module__, self.__class__.__name__]) return '.'.join([self.__module__, self.__class__.__name__])
def _log(self, obj, message, level=LOG_DEFAULT): def _log(self, obj, message, level=LogLevelChoices.LOG_DEFAULT):
""" """
Log a message from a test method. Do not call this method directly; use one of the log_* wrappers below. Log a message from a test method. Do not call this method directly; use one of the log_* wrappers below.
""" """
if level not in LOG_LEVEL_CODES: if level not in LogLevelChoices.as_dict():
raise Exception("Unknown logging level: {}".format(level)) raise Exception("Unknown logging level: {}".format(level))
self._results[self.active_test]['log'].append(( self._results[self.active_test]['log'].append((
timezone.now().isoformat(), timezone.now().isoformat(),
LOG_LEVEL_CODES.get(level), level,
str(obj) if obj else None, str(obj) if obj else None,
obj.get_absolute_url() if getattr(obj, 'get_absolute_url', None) else None, obj.get_absolute_url() if getattr(obj, 'get_absolute_url', None) else None,
message, message,
@ -171,7 +171,7 @@ class Report(object):
""" """
Log a message which is not associated with a particular object. Log a message which is not associated with a particular object.
""" """
self._log(None, message, level=LOG_DEFAULT) self._log(None, message, level=LogLevelChoices.LOG_DEFAULT)
self.logger.info(message) self.logger.info(message)
def log_success(self, obj, message=None): def log_success(self, obj, message=None):
@ -179,7 +179,7 @@ class Report(object):
Record a successful test against an object. Logging a message is optional. Record a successful test against an object. Logging a message is optional.
""" """
if message: if message:
self._log(obj, message, level=LOG_SUCCESS) self._log(obj, message, level=LogLevelChoices.LOG_SUCCESS)
self._results[self.active_test]['success'] += 1 self._results[self.active_test]['success'] += 1
self.logger.info(f"Success | {obj}: {message}") self.logger.info(f"Success | {obj}: {message}")
@ -187,7 +187,7 @@ class Report(object):
""" """
Log an informational message. Log an informational message.
""" """
self._log(obj, message, level=LOG_INFO) self._log(obj, message, level=LogLevelChoices.LOG_INFO)
self._results[self.active_test]['info'] += 1 self._results[self.active_test]['info'] += 1
self.logger.info(f"Info | {obj}: {message}") self.logger.info(f"Info | {obj}: {message}")
@ -195,7 +195,7 @@ class Report(object):
""" """
Log a warning. Log a warning.
""" """
self._log(obj, message, level=LOG_WARNING) self._log(obj, message, level=LogLevelChoices.LOG_WARNING)
self._results[self.active_test]['warning'] += 1 self._results[self.active_test]['warning'] += 1
self.logger.info(f"Warning | {obj}: {message}") self.logger.info(f"Warning | {obj}: {message}")
@ -203,7 +203,7 @@ class Report(object):
""" """
Log a failure. Calling this method will automatically mark the report as failed. Log a failure. Calling this method will automatically mark the report as failed.
""" """
self._log(obj, message, level=LOG_FAILURE) self._log(obj, message, level=LogLevelChoices.LOG_FAILURE)
self._results[self.active_test]['failure'] += 1 self._results[self.active_test]['failure'] += 1
self.logger.info(f"Failure | {obj}: {message}") self.logger.info(f"Failure | {obj}: {message}")
self.failed = True self.failed = True

View File

@ -19,11 +19,10 @@ from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
from mptt.models import MPTTModel from mptt.models import MPTTModel
from extras.api.serializers import ScriptOutputSerializer from extras.api.serializers import ScriptOutputSerializer
from extras.choices import JobResultStatusChoices from extras.choices import JobResultStatusChoices, LogLevelChoices
from extras.models import JobResult from extras.models import JobResult
from ipam.formfields import IPAddressFormField, IPNetworkFormField from ipam.formfields import IPAddressFormField, IPNetworkFormField
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING
from utilities.exceptions import AbortTransaction from utilities.exceptions import AbortTransaction
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
from .forms import ScriptForm from .forms import ScriptForm
@ -324,23 +323,23 @@ class BaseScript:
def log_debug(self, message): def log_debug(self, message):
self.logger.log(logging.DEBUG, message) self.logger.log(logging.DEBUG, message)
self.log.append((LOG_DEFAULT, message)) self.log.append((LogLevelChoices.LOG_DEFAULT, message))
def log_success(self, message): def log_success(self, message):
self.logger.log(logging.INFO, message) # No syslog equivalent for SUCCESS self.logger.log(logging.INFO, message) # No syslog equivalent for SUCCESS
self.log.append((LOG_SUCCESS, message)) self.log.append((LogLevelChoices.LOG_SUCCESS, message))
def log_info(self, message): def log_info(self, message):
self.logger.log(logging.INFO, message) self.logger.log(logging.INFO, message)
self.log.append((LOG_INFO, message)) self.log.append((LogLevelChoices.LOG_INFO, message))
def log_warning(self, message): def log_warning(self, message):
self.logger.log(logging.WARNING, message) self.logger.log(logging.WARNING, message)
self.log.append((LOG_WARNING, message)) self.log.append((LogLevelChoices.LOG_WARNING, message))
def log_failure(self, message): def log_failure(self, message):
self.logger.log(logging.ERROR, message) self.logger.log(logging.ERROR, message)
self.log.append((LOG_FAILURE, message)) self.log.append((LogLevelChoices.LOG_FAILURE, message))
# Convenience functions # Convenience functions
@ -428,11 +427,15 @@ def run_script(data, request, commit=True, *args, **kwargs):
try: try:
with transaction.atomic(): with transaction.atomic():
script.output = script.run(**kwargs) script.output = script.run(**kwargs)
job_result.status = JobResultStatusChoices.STATUS_COMPLETED job_result.data = ScriptOutputSerializer(script).data
job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
if not commit: if not commit:
raise AbortTransaction() raise AbortTransaction()
except AbortTransaction: except AbortTransaction:
pass pass
except Exception as e: except Exception as e:
stacktrace = traceback.format_exc() stacktrace = traceback.format_exc()
script.log_failure( script.log_failure(
@ -440,7 +443,8 @@ def run_script(data, request, commit=True, *args, **kwargs):
) )
logger.error(f"Exception raised during script execution: {e}") logger.error(f"Exception raised during script execution: {e}")
commit = False commit = False
job_result.status = JobResultStatusChoices.STATUS_FAILED job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
finally: finally:
if not commit: if not commit:
# Delete all pending changelog entries # Delete all pending changelog entries
@ -449,10 +453,6 @@ def run_script(data, request, commit=True, *args, **kwargs):
"Database changes have been reverted automatically." "Database changes have been reverted automatically."
) )
job_result.data = ScriptOutputSerializer(script).data
job_result.completed = timezone.now()
job_result.save()
logger.info(f"Script completed in {job_result.duration}") logger.info(f"Script completed in {job_result.duration}")
# Delete any previous terminal state results # Delete any previous terminal state results

View File

@ -1,6 +1,6 @@
from django import template from django import template
from extras.constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING from extras.choices import LogLevelChoices
register = template.Library() register = template.Library()
@ -11,28 +11,7 @@ def log_level(level):
""" """
Display a label indicating a syslog severity (e.g. info, warning, etc.). Display a label indicating a syslog severity (e.g. info, warning, etc.).
""" """
# TODO: we should convert this to a choices class return {
levels = { 'name': LogLevelChoices.as_dict()[level],
'default': { 'class': dict(LogLevelChoices.CLASS_MAP)[level]
'name': 'Default',
'class': 'default'
},
'success': {
'name': 'Success',
'class': 'success',
},
'info': {
'name': 'Info',
'class': 'info'
},
'warning': {
'name': 'Warning',
'class': 'warning'
},
'failure': {
'name': 'Failure',
'class': 'danger'
}
} }
return levels[level]

View File

@ -453,8 +453,22 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
def get(self, request): def get(self, request):
scripts = get_scripts(use_names=True)
script_content_type = ContentType.objects.get(app_label='extras', model='script')
results = {
r.name: r
for r in JobResult.objects.filter(
obj_type=script_content_type,
status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
).defer('data')
}
for _scripts in scripts.values():
for script in _scripts.values():
script.result = results.get(script.full_name)
return render(request, 'extras/script_list.html', { return render(request, 'extras/script_list.html', {
'scripts': get_scripts(use_names=True), 'scripts': scripts,
}) })

View File

@ -3,10 +3,8 @@ var timeout = 1000;
function updatePendingStatusLabel(status){ function updatePendingStatusLabel(status){
var labelClass; var labelClass;
if (status.value === 'failed'){ if (status.value === 'failed' || status.value === 'errored'){
labelClass = 'danger'; labelClass = 'danger';
} else if (status.value === 'pending'){
labelClass = 'default';
} else if (status.value === 'running'){ } else if (status.value === 'running'){
labelClass = 'warning'; labelClass = 'warning';
} else if (status.value === 'completed'){ } else if (status.value === 'completed'){
@ -33,7 +31,7 @@ $(document).ready(function(){
context: this, context: this,
success: function(data) { success: function(data) {
updatePendingStatusLabel(data.status); updatePendingStatusLabel(data.status);
if (data.status.value === 'completed' || data.status.value === 'failed'){ if (data.status.value === 'completed' || data.status.value === 'failed' || data.status.value === 'errored'){
jobTerminatedAction() jobTerminatedAction()
} else { } else {
setTimeout(checkPendingResult, timeout); setTimeout(checkPendingResult, timeout);

View File

@ -1,11 +1,13 @@
{% if result.status == 'failed' %} {% if result.status == 'failed' %}
<label class="label label-danger">Failed</label> <label class="label label-danger">Failed</label>
{% elif result.status == 'errored' %}
<label class="label label-danger">Errored</label>
{% elif result.status == 'pending' %} {% elif result.status == 'pending' %}
<label class="label label-default">Pending</label> <label class="label label-default">Pending</label>
{% elif result.status == 'running' %} {% elif result.status == 'running' %}
<label class="label label-warning">Running</label> <label class="label label-warning">Running</label>
{% elif result.status == 'completed' %} {% elif result.status == 'completed' %}
<label class="label label-success">Passed</label> <label class="label label-success">Completed</label>
{% else %} {% else %}
<label class="label label-default">N/A</label> <label class="label label-default">N/A</label>
{% endif %} {% endif %}

View File

@ -38,7 +38,7 @@
{% endif %} {% endif %}
<span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span> <span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
</p> </p>
{% if result.completed %} {% if result.completed and result.status != 'errored' %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Report Methods</strong> <strong>Report Methods</strong>
@ -97,8 +97,10 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% elif result.status == 'errored' %}
<div class="well">Error during report execution</div>
{% else %} {% else %}
<div class="well">Pending results</div> <div class="well">Pending results</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -4,15 +4,17 @@
{% block content %} {% block content %}
<h1>{% block title %}Scripts{% endblock %}</h1> <h1>{% block title %}Scripts{% endblock %}</h1>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-9">
{% if scripts %} {% if scripts %}
{% for module, module_scripts in scripts.items %} {% for module, module_scripts in scripts.items %}
<h3><a name="module.{{ module }}"></a>{{ module|bettertitle }}</h3> <h3><a name="module.{{ module }}"></a>{{ module|bettertitle }}</h3>
<table class="table table-hover table-headings reports"> <table class="table table-hover table-headings reports">
<thead> <thead>
<tr> <tr>
<th class="col-md-3">Name</th> <th>Name</th>
<th class="col-md-9">Description</th> <th>Status</th>
<th>Description</th>
<th class="text-right">Last Run</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -21,7 +23,15 @@
<td> <td>
<a href="{% url 'extras:script' module=script.module name=class_name %}" name="script.{{ class_name }}"><strong>{{ script }}</strong></a> <a href="{% url 'extras:script' module=script.module name=class_name %}" name="script.{{ class_name }}"><strong>{{ script }}</strong></a>
</td> </td>
<td>
{% include 'extras/inc/job_label.html' with result=script.result %}
</td>
<td>{{ script.Meta.description }}</td> <td>{{ script.Meta.description }}</td>
{% if script.result %}
<td class="text-right">{{ script.result.created }}</td>
{% else %}
<td class="text-right text-muted">Never</td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -34,5 +44,26 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-3">
{% if scripts %}
<div class="panel panel-default">
{% for module, module_scripts in scripts.items %}
<div class="panel-heading">
<strong>{{ module|bettertitle }}</strong>
</div>
<ul class="list-group">
{% for class_name, script in module_scripts.items %}
<a href="#script.{{ class_name }}" class="list-group-item">
<i class="fa fa-list-alt"></i> {{ script.name }}
<div class="pull-right">
{% include 'extras/inc/job_label.html' with result=script.result %}
</div>
</a>
{% endfor %}
</ul>
{% endfor %}
</div>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -41,7 +41,7 @@
<span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span> <span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
</p> </p>
<div role="tabpanel" class="tab-pane active" id="log"> <div role="tabpanel" class="tab-pane active" id="log">
{% if result.completed %} {% if result.completed and result.status != 'errored' %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
@ -76,6 +76,12 @@
</div> </div>
</div> </div>
</div> </div>
{% elif result.stats == 'errored' %}
<div class="row">
<div class="col-md-12">
<div class="well">Error during script execution</div>
</div>
</div>
{% else %} {% else %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">