diff --git a/netbox/core/jobs.py b/netbox/core/jobs.py index ee285fa7c..8ef8c4e72 100644 --- a/netbox/core/jobs.py +++ b/netbox/core/jobs.py @@ -22,8 +22,9 @@ def sync_datasource(job_result, *args, **kwargs): # Update the search cache for DataFiles belonging to this source search_backend.cache(datasource.datafiles.iterator()) + job_result.terminate() + except SyncError as e: - job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) - job_result.save() + job_result.terminate(status=JobResultStatusChoices.STATUS_ERRORED) DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED) logging.error(e) diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py index ae49d53be..b10a4644d 100644 --- a/netbox/extras/management/commands/runscript.py +++ b/netbox/extras/management/commands/runscript.py @@ -41,16 +41,16 @@ class Command(BaseCommand): the change_logging context manager (which is bypassed if commit == False). """ try: - with transaction.atomic(): - script.output = script.run(data=data, commit=commit) - job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED) - - if not commit: - raise AbortTransaction() - - except AbortTransaction: - script.log_info("Database changes have been reverted automatically.") - clear_webhooks.send(request) + try: + with transaction.atomic(): + script.output = script.run(data=data, commit=commit) + if not commit: + raise AbortTransaction() + except AbortTransaction: + script.log_info("Database changes have been reverted automatically.") + clear_webhooks.send(request) + job_result.data = ScriptOutputSerializer(script).data + job_result.terminate() except Exception as e: stacktrace = traceback.format_exc() script.log_failure( @@ -58,11 +58,9 @@ class Command(BaseCommand): ) script.log_info("Database changes have been reverted due to error.") logger.error(f"Exception raised during script execution: {e}") - job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) clear_webhooks.send(request) - finally: job_result.data = ScriptOutputSerializer(script).data - job_result.save() + job_result.terminate(status=JobResultStatusChoices.STATUS_ERRORED) logger.info(f"Script completed in {job_result.duration}") diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index e6d209941..4de0e3ef2 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -694,14 +694,16 @@ class JobResult(models.Model): self.status = JobResultStatusChoices.STATUS_RUNNING JobResult.objects.filter(pk=self.pk).update(started=self.started, status=self.status) - def set_status(self, status): + def terminate(self, status=JobResultStatusChoices.STATUS_COMPLETED): """ - Helper method to change the status of the job result. If the target status is terminal, the completion - time is also set. + Mark the job as completed, optionally specifying a particular termination status. """ + valid_statuses = JobResultStatusChoices.TERMINAL_STATE_CHOICES + if status not in valid_statuses: + raise ValueError(f"Invalid status for job termination. Choices are: {', '.join(valid_statuses)}") self.status = status - if status in JobResultStatusChoices.TERMINAL_STATE_CHOICES: - self.completed = timezone.now() + self.completed = timezone.now() + JobResult.objects.filter(pk=self.pk).update(status=self.status, completed=self.completed) @classmethod def enqueue_job(cls, func, name, obj_type, user, schedule_at=None, interval=None, *args, **kwargs): diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index 37c78dd18..0a944a0d2 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -85,8 +85,7 @@ def run_report(job_result, *args, **kwargs): job_result.start() report.run(job_result) except Exception: - job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) - job_result.save() + job_result.terminate(status=JobResultStatusChoices.STATUS_ERRORED) logging.error(f"Error during execution of report {job_result.name}") finally: # Schedule the next job if an interval has been set @@ -241,28 +240,23 @@ class Report(object): self.pre_run() try: - for method_name in self.test_methods: self.active_test = method_name test_method = getattr(self, method_name) test_method() - 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}
{stacktrace}")
logger.error(f"Exception raised during report execution: {e}")
- job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
-
- job_result.data = self._results
- job_result.completed = timezone.now()
- job_result.save()
+ job_result.terminate(status=JobResultStatusChoices.STATUS_ERRORED)
+ finally:
+ job_result.terminate()
# Perform any post-run tasks
self.post_run()
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
index 313058d57..9b9167e17 100644
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -460,36 +460,28 @@ def run_script(data, request, commit=True, *args, **kwargs):
the change_logging context manager (which is bypassed if commit == False).
"""
try:
- with transaction.atomic():
- script.output = script.run(data=data, commit=commit)
- job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
-
- if not commit:
- raise AbortTransaction()
-
- except AbortTransaction:
- script.log_info("Database changes have been reverted automatically.")
- clear_webhooks.send(request)
- except AbortScript as e:
- script.log_failure(
- f"Script aborted with error: {e}"
- )
- script.log_info("Database changes have been reverted due to error.")
- logger.error(f"Script aborted with error: {e}")
- job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
- clear_webhooks.send(request)
- except Exception as e:
- stacktrace = traceback.format_exc()
- script.log_failure(
- 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}")
- job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
- clear_webhooks.send(request)
- finally:
+ try:
+ with transaction.atomic():
+ script.output = script.run(data=data, commit=commit)
+ if not commit:
+ raise AbortTransaction()
+ except AbortTransaction:
+ script.log_info("Database changes have been reverted automatically.")
+ clear_webhooks.send(request)
job_result.data = ScriptOutputSerializer(script).data
- job_result.save()
+ job_result.terminate()
+ except Exception as e:
+ if type(e) is AbortScript:
+ script.log_failure(f"Script aborted with error: {e}")
+ logger.error(f"Script aborted with error: {e}")
+ else:
+ stacktrace = traceback.format_exc()
+ script.log_failure(f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```")
+ logger.error(f"Exception raised during script execution: {e}")
+ script.log_info("Database changes have been reverted due to error.")
+ job_result.data = ScriptOutputSerializer(script).data
+ job_result.terminate(status=JobResultStatusChoices.STATUS_ERRORED)
+ clear_webhooks.send(request)
logger.info(f"Script completed in {job_result.duration}")