1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Fixes #5012: Return details of exceptions resulting from report/script execution

This commit is contained in:
Jeremy Stretch
2020-08-20 12:47:26 -04:00
parent bf4fee1592
commit bc0e6cc8dd
6 changed files with 33 additions and 39 deletions

View File

@ -6,6 +6,7 @@
* [#4990](https://github.com/netbox-community/netbox/issues/4990) - Restore change logging during custom script execution * [#4990](https://github.com/netbox-community/netbox/issues/4990) - Restore change logging during custom script execution
* [#5004](https://github.com/netbox-community/netbox/issues/5004) - Permit assignment of an interface to a LAG on any peer virtual chassis member * [#5004](https://github.com/netbox-community/netbox/issues/5004) - Permit assignment of an interface to a LAG on any peer virtual chassis member
* [#5012](https://github.com/netbox-community/netbox/issues/5012) - Return details of exceptions resulting from report/script execution
* [#5020](https://github.com/netbox-community/netbox/issues/5020) - Correct handling of dependent objects during bulk deletion * [#5020](https://github.com/netbox-community/netbox/issues/5020) - Correct handling of dependent objects during bulk deletion
--- ---

View File

@ -652,15 +652,13 @@ class JobResult(models.Model):
def set_status(self, status): def set_status(self, status):
""" """
Helper method to change the status of the job result and save. If the target status is terminal, the Helper method to change the status of the job result. If the target status is terminal, the completion
completion time is also set. time is also set.
""" """
self.status = status self.status = status
if status in JobResultStatusChoices.TERMINAL_STATE_CHOICES: if status in JobResultStatusChoices.TERMINAL_STATE_CHOICES:
self.completed = timezone.now() self.completed = timezone.now()
self.save()
@classmethod @classmethod
def enqueue_job(cls, func, name, obj_type, user, *args, **kwargs): def enqueue_job(cls, func, name, obj_type, user, *args, **kwargs):
""" """

View File

@ -2,10 +2,10 @@ import importlib
import inspect import inspect
import logging import logging
import pkgutil import pkgutil
import traceback
from collections import OrderedDict from collections import OrderedDict
from django.conf import settings from django.conf import settings
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
@ -79,6 +79,7 @@ def run_report(job_result, *args, **kwargs):
except Exception as e: except Exception as e:
print(e) print(e)
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
job_result.save()
logging.error(f"Error during execution of report {job_result.name}") logging.error(f"Error during execution of report {job_result.name}")
# Delete any previous terminal state results # Delete any previous terminal state results
@ -170,7 +171,7 @@ class Report(object):
timezone.now().isoformat(), timezone.now().isoformat(),
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 hasattr(obj, 'get_absolute_url') else None,
message, message,
)) ))
@ -223,17 +224,25 @@ class Report(object):
job_result.status = JobResultStatusChoices.STATUS_RUNNING job_result.status = JobResultStatusChoices.STATUS_RUNNING
job_result.save() job_result.save()
for method_name in self.test_methods: try:
self.active_test = method_name
test_method = getattr(self, method_name)
test_method()
if self.failed: for method_name in self.test_methods:
self.logger.warning("Report failed") self.active_test = method_name
job_result.status = JobResultStatusChoices.STATUS_FAILED test_method = getattr(self, method_name)
else: test_method()
self.logger.info("Report completed successfully")
job_result.status = JobResultStatusChoices.STATUS_COMPLETED if self.failed:
self.logger.warning("Report failed")
job_result.status = JobResultStatusChoices.STATUS_FAILED
else:
self.logger.info("Report completed successfully")
job_result.status = JobResultStatusChoices.STATUS_COMPLETED
except Exception as e:
stacktrace = traceback.format_exc()
self.log_failure(None, f"An exception occurred: {type(e).__name__}: {e} <pre>{stacktrace}</pre>")
logger.error(f"Exception raised during report execution: {e}")
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
job_result.data = self._results job_result.data = self._results
job_result.completed = timezone.now() job_result.completed = timezone.now()

View File

@ -446,32 +446,26 @@ 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.set_status(JobResultStatusChoices.STATUS_COMPLETED)
if not commit: if not commit:
raise AbortTransaction() raise AbortTransaction()
except AbortTransaction: except AbortTransaction:
pass script.log_info("Database changes have been reverted automatically.")
except Exception as e: except Exception as e:
stacktrace = traceback.format_exc() stacktrace = traceback.format_exc()
script.log_failure( script.log_failure(
"An exception occurred: `{}: {}`\n```\n{}\n```".format(type(e).__name__, e, stacktrace) f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
) )
script.log_info("Database changes have been reverted due to error.")
logger.error(f"Exception raised during script execution: {e}") logger.error(f"Exception raised during script execution: {e}")
commit = False
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
finally: finally:
if job_result.status != JobResultStatusChoices.STATUS_ERRORED: job_result.data = ScriptOutputSerializer(script).data
job_result.data = ScriptOutputSerializer(script).data job_result.save()
job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
if not commit:
# Delete all pending changelog entries
script.log_info(
"Database changes have been reverted automatically."
)
logger.info(f"Script completed in {job_result.duration}") logger.info(f"Script completed in {job_result.duration}")

View File

@ -16,7 +16,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 and result.status != 'errored' %} {% if result.completed %}
<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>
@ -75,10 +75,8 @@
</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

@ -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 and result.status != 'errored' %} {% if result.completed %}
<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,12 +76,6 @@
</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">