From c73c675812cf149058805efc5f0326d8c3cb3832 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Thu, 13 Jun 2024 13:06:52 +0200 Subject: [PATCH] Ensure that a failed deployment is not created (#172) * tests: add a test to make sure that a failed deployment is not created * fix: rollback the whole changes in a Design Job if something fails --- nautobot_design_builder/design_job.py | 4 ++-- .../templates/simple_design_with_error.yaml.j2 | 4 ++++ .../tests/designs/test_designs.py | 13 ++++++++++++- nautobot_design_builder/tests/test_design_job.py | 9 +++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 diff --git a/nautobot_design_builder/design_job.py b/nautobot_design_builder/design_job.py index 61c8e4f..a56c417 100644 --- a/nautobot_design_builder/design_job.py +++ b/nautobot_design_builder/design_job.py @@ -268,6 +268,8 @@ def _run_in_transaction(self, **kwargs): # pylint: disable=too-many-branches, t This version of `run` is wrapped in a transaction and will roll back database changes on error. In general, this method should only be called by the `run` method. """ + sid = transaction.savepoint() + self.log_info(message=f"Building {getattr(self.Meta, 'name')}") extensions = getattr(self.Meta, "extensions", []) @@ -307,8 +309,6 @@ def _run_in_transaction(self, **kwargs): # pylint: disable=too-many-branches, t self.failed = True return - sid = transaction.savepoint() - try: for design_file in design_files: self.implement_design(context, design_file, commit) diff --git a/nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 b/nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 new file mode 100644 index 0000000..04f4c5e --- /dev/null +++ b/nautobot_design_builder/tests/designs/templates/simple_design_with_error.yaml.j2 @@ -0,0 +1,4 @@ +--- +manufacturers: + name: "Test Manufacturer" + wrong: "attribute" diff --git a/nautobot_design_builder/tests/designs/test_designs.py b/nautobot_design_builder/tests/designs/test_designs.py index 775be58..ee1fcb4 100644 --- a/nautobot_design_builder/tests/designs/test_designs.py +++ b/nautobot_design_builder/tests/designs/test_designs.py @@ -50,9 +50,20 @@ class Meta: # pylint: disable=too-few-public-methods ] -class MultiDesignJobWithError(DesignJob): +class DesignJobModeDeploymentWithError(DesignJob): """Design job that includes an error (for unit testing).""" + class Meta: # pylint: disable=too-few-public-methods + name = "File Design with Error" + design_files = [ + "templates/simple_design_with_error.yaml.j2", + ] + design_mode = DesignModeChoices.DEPLOYMENT + + +class MultiDesignJobWithError(DesignJob): + """Multi Design job that includes an error (for unit testing).""" + class Meta: # pylint: disable=too-few-public-methods name = "Multi File Design with Error" design_files = [ diff --git a/nautobot_design_builder/tests/test_design_job.py b/nautobot_design_builder/tests/test_design_job.py index 48dc211..4e2eda3 100644 --- a/nautobot_design_builder/tests/test_design_job.py +++ b/nautobot_design_builder/tests/test_design_job.py @@ -9,6 +9,7 @@ from nautobot.ipam.models import VRF, Prefix, IPAddress from nautobot.extras.models import Status +from nautobot_design_builder.models import Deployment from nautobot_design_builder.errors import DesignImplementationError, DesignValidationError from nautobot_design_builder.tests import DesignTestCase from nautobot_design_builder.tests.designs import test_designs @@ -27,6 +28,14 @@ def test_simple_design_rollback(self): job.run(data=self.data, commit=True) self.assertEqual(0, Manufacturer.objects.all().count()) + def test_simple_design_rollback_deployment_mode(self): + """Confirm that database changes are rolled back when an exception is raised and no Design Deployment is created.""" + self.assertEqual(0, Manufacturer.objects.all().count()) + job = self.get_mocked_job(test_designs.DesignJobModeDeploymentWithError) + job.run(data={**self.data, **{"deployment_name": "whatever"}}, commit=True) + self.assertEqual(0, Manufacturer.objects.all().count()) + self.assertEqual(0, Deployment.objects.all().count()) + def test_simple_design_report(self): """Confirm that a report is generated.""" job = self.get_mocked_job(test_designs.SimpleDesignReport)