diff --git a/development/development.env b/development/development.env index 7613c1e2..c81fe526 100644 --- a/development/development.env +++ b/development/development.env @@ -37,3 +37,5 @@ POSTGRES_DB=${NAUTOBOT_DB_NAME} MYSQL_USER=${NAUTOBOT_DB_USER} MYSQL_DATABASE=${NAUTOBOT_DB_NAME} MYSQL_ROOT_HOST=% + +DESIGN_BUILDER_ENABLE_BGP=True diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index bfd1ddcb..dc2641a2 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,31 +1,14 @@ -# We can't remove volumes in a compose override, for the test configuration using the final containers -# we don't want the volumes so this is the default override file to add the volumes in the dev case -# any override will need to include these volumes to use them. -# see: https://github.com/docker/compose/issues/3729 --- version: "3.8" + services: nautobot: - command: "nautobot-server runserver 0.0.0.0:8080" - ports: - - "8080:8080" - volumes: - - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - - "../:/source" - - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" - - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" - healthcheck: - test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test - docs: - entrypoint: "mkdocs serve -v -a 0.0.0.0:8080" - ports: - - "8001:8080" - volumes: - - "../:/source" - image: "nautobot-design-builder/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" - healthcheck: - disable: true - tty: true + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" worker: environment: - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" @@ -33,27 +16,25 @@ services: - "development.env" - "creds.env" - "development_mysql.env" + beat: + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" db: image: "mysql:8" command: - "--default-authentication-plugin=mysql_native_password" - - "--max_connections=1000" env_file: - "development.env" - "creds.env" - "development_mysql.env" volumes: - - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - - "../:/source" - - "../examples/backbone_design/designs:/opt/nautobot/designs:cached" - - "../examples/backbone_design/jobs:/opt/nautobot/jobs:cached" + - "mysql_data:/var/lib/mysql" healthcheck: - test: - - "CMD" - - "mysqladmin" - - "ping" - - "-h" - - "localhost" + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: "20s" retries: 10 volumes: diff --git a/development/nautobot_config.py b/development/nautobot_config.py index e9959dc2..43d06a57 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,11 +1,13 @@ """Nautobot development configuration file.""" +from importlib import metadata +from importlib.util import find_spec import os import sys +from packaging.version import Version + from nautobot.core.settings import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import from nautobot.core.settings_funcs import is_truthy, parse_redis_connection -from importlib import metadata -from packaging.version import Version # # Debug @@ -136,11 +138,29 @@ # Apps configuration settings. These settings are used by various Apps that the user may have installed. # Each key in the dictionary is the name of an installed App and its value is a dictionary of settings. -# TODO: The following is necessary only until BGP models plugin -# is officially supported in 2.0 -nautobot_version = Version(Version(metadata.version("nautobot")).base_version) - -if nautobot_version < Version("2.0"): +if is_truthy(os.getenv("DESIGN_BUILDER_ENABLE_BGP", "False")): + if find_spec("nautobot_bgp_models") is None: + nautobot_version = Version(Version(metadata.version("nautobot")).base_version) + import subprocess # nosec + + package = "nautobot-bgp-models" + if nautobot_version < Version("2.0"): + # pip doesn't have the capability to automatically pick + # a version compatible with the current Nautobot, so we + # do that manually. This is only for development environments + # so that we can run unit tests on Nautobot 1.6 and 2.x + package = "nautobot-bgp-models==0.9.1" + command = [ + sys.executable, + "-m", + "pip", + "install", + package, + ] + # Since we aren't evaluating any input variables, *and* we + # are not using a shell, this command should be considered + # save, therefore the `nosec` annotation. + subprocess.check_call(command, shell=False) # nosec PLUGINS.append("nautobot_bgp_models") PLUGINS_CONFIG = {"design_builder": {"context_repository": os.getenv("DESIGN_BUILDER_CONTEXT_REPO_SLUG", None)}} diff --git a/nautobot_design_builder/contrib/tests/test_ext.py b/nautobot_design_builder/contrib/tests/test_ext.py index 095bd1b1..617be7fb 100644 --- a/nautobot_design_builder/contrib/tests/test_ext.py +++ b/nautobot_design_builder/contrib/tests/test_ext.py @@ -1,398 +1,32 @@ """Unit tests related to template extensions.""" -import yaml +import os -from django.db.models import Q from django.test import TestCase -from nautobot.extras.models import Status -from nautobot.dcim.models import Interface, Device, DeviceType -from nautobot.tenancy.models import Tenant -from nautobot.ipam.models import Prefix - -from nautobot_design_builder.contrib.ext import ( - BGPPeeringExtension, - ChildPrefixExtension, - LookupExtension, - CableConnectionExtension, - NextPrefixExtension, -) -from nautobot_design_builder.design import Builder +from nautobot_design_builder.tests.test_builder import builder_test_case from nautobot_design_builder.util import nautobot_version -class TestLookupExtension(TestCase): - """Test Lookup Extension.""" - - def test_lookup_by_dict(self): - design_template = """ - manufacturers: - - name: "Manufacturer" - - device_types: - - "!lookup:manufacturer": - name: "Manufacturer" - model: "model" - """ - design = yaml.safe_load(design_template) - builder = Builder(extensions=[LookupExtension]) - builder.implement_design(design, commit=True) - device_type = DeviceType.objects.get(model="model") - self.assertEqual("Manufacturer", device_type.manufacturer.name) - - def test_lookup_by_single_attribute(self): - design_template = """ - manufacturers: - - name: "Manufacturer" - - device_types: - - "!lookup:manufacturer:name": "Manufacturer" - model: "model" - """ - design = yaml.safe_load(design_template) - builder = Builder(extensions=[LookupExtension]) - builder.implement_design(design, commit=True) - device_type = DeviceType.objects.get(model="model") - self.assertEqual("Manufacturer", device_type.manufacturer.name) - - -class TestCableConnectionExtension(TestCase): - """Test Cable Connection Extension.""" - - def test_connect_cable(self): - design_template_v1 = """ - sites: - - "!create_or_update:name": "Site" - status__name: "Active" - device_roles: - - "!create_or_update:name": "test-role" - manufacturers: - - "!create_or_update:name": "test-manufacturer" - device_types: - - manufacturer__name: "test-manufacturer" - "!create_or_update:model": "test-type" - devices: - - "!create_or_update:name": "Device 1" - "!ref": "device1" - site__name: "Site" - status__name: "Active" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - - "!create_or_update:name": "Device 2" - site__name: "Site" - status__name: "Active" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - "!connect_cable": - status__name: "Planned" - to: - device: "!ref:device1" - name: "GigabitEthernet1" - """ - - design_template_v2 = """ - location_types: - - "!create_or_update:name": "Site" - content_types: - - "!get:app_label": "dcim" - "!get:model": "device" - locations: - - location_type__name: "Site" - "!create_or_update:name": "Site" - status__name: "Active" - roles: - - "!create_or_update:name": "test-role" - content_types: - - "!get:app_label": "dcim" - "!get:model": "device" - manufacturers: - - "!create_or_update:name": "test-manufacturer" - device_types: - - manufacturer__name: "test-manufacturer" - "!create_or_update:model": "test-type" - devices: - - "!create_or_update:name": "Device 1" - "!ref": "device1" - location__name: "Site" - status__name: "Active" - role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - - "!create_or_update:name": "Device 2" - location__name: "Site" - status__name: "Active" - role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "GigabitEthernet1" - type: "1000base-t" - status__name: "Active" - "!connect_cable": - status__name: "Planned" - to: - device: "!ref:device1" - name: "GigabitEthernet1" - """ - - if nautobot_version < "2.0.0": - design = yaml.safe_load(design_template_v1) - else: - design = yaml.safe_load(design_template_v2) - - # test idempotence by running it twice: - for _ in range(2): - builder = Builder(extensions=[CableConnectionExtension]) - builder.implement_design(design, commit=True) - interfaces = Interface.objects.all() - self.assertEqual(2, len(interfaces)) - self.assertEqual(interfaces[0].connected_endpoint, interfaces[1]) - self.assertIsNotNone(interfaces[0]._path_id) # pylint: disable=protected-access - self.assertIsNotNone(interfaces[1]._path_id) # pylint: disable=protected-access - - -class PrefixExtensionTests(TestCase): - """Base class for testing prefix based extensions.""" - - @staticmethod - def create_prefix(prefix, **kwargs): - """Affirm the existence of a prefix and return it.""" - prefix, _ = Prefix.objects.get_or_create( - prefix=prefix, - defaults={ - "status": Status.objects.get(name="Active"), - **kwargs, - }, - ) - return prefix - - def setUp(self) -> None: - self.tenant = Tenant.objects.create(name="Nautobot Airports") - if nautobot_version < "2.0.0": - from nautobot.ipam.models import Role # pylint: disable=no-name-in-module,import-outside-toplevel - else: - from nautobot.extras.models import Role # pylint: disable=no-name-in-module,import-outside-toplevel - - self.server_role = Role.objects.create(name="servers") - self.video_role = Role.objects.create(name="video") - self.prefixes = [] - self.prefixes.append(PrefixExtensionTests.create_prefix("10.0.0.0/8", tenant=self.tenant)) - self.prefixes.append( - PrefixExtensionTests.create_prefix("10.0.0.0/23", tenant=self.tenant, role=self.server_role) - ) - self.prefixes.append( - PrefixExtensionTests.create_prefix("10.0.2.0/23", tenant=self.tenant, role=self.video_role) - ) - - -class TestNextPrefixExtension(PrefixExtensionTests): - """Test Next Prefix Extension.""" +@builder_test_case(os.path.join(os.path.dirname(__file__), "testdata")) +class TestAgnosticExtensions(TestCase): + """Test contrib extensions against any version of Nautobot.""" - def test_next_prefix_lookup(self): - extension = NextPrefixExtension(None) - want = "10.0.4.0/24" - got = extension._get_next([self.prefixes[0]], "24") # pylint:disable=protected-access - self.assertEqual(want, got) - def test_next_prefix_lookup_from_full_prefix(self): - for prefix in ["10.0.0.0/24", "10.0.1.0/24"]: - PrefixExtensionTests.create_prefix(prefix) - - prefixes = Prefix.objects.filter( - Q(network="10.0.0.0", prefix_length=23) | Q(network="10.0.2.0", prefix_length=23) - ) - - extension = NextPrefixExtension(None) - want = "10.0.2.0/24" - got = extension._get_next(prefixes, "24") # pylint:disable=protected-access - self.assertEqual(want, got) - - def test_creation(self): - design_template = """ - prefixes: - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - - "!next_prefix": - prefix: - - "10.0.0.0/23" - - "10.0.2.0/23" - length: 24 - status__name: "Active" - """ - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[NextPrefixExtension]) - object_creator.implement_design(design, commit=True) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.1.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.2.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.3.0/24").exists()) - - def test_lookup_by_role_and_tenant(self): - design_template = """ - prefixes: - - "!next_prefix": - role__name: "video" - tenant__name: "Nautobot Airports" - length: 24 - status__name: "Active" - """ - self.assertFalse(Prefix.objects.filter(prefix="10.0.2.0/24").exists()) - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[NextPrefixExtension]) - object_creator.implement_design(design, commit=True) - self.assertTrue(Prefix.objects.filter(prefix="10.0.2.0/24").exists()) - - -class TestChildPrefixExtension(PrefixExtensionTests): - """Test Child Prefix Extension.""" - - def test_creation(self): - design_template = """ - prefixes: - - "!next_prefix": - prefix: - - "10.0.0.0/23" - length: 24 - status__name: "Active" - "!ref": "parent_prefix" - - "!child_prefix": - parent: "!ref:parent_prefix" - offset: "0.0.0.0/25" - status__name: "Active" - - "!child_prefix": - parent: "!ref:parent_prefix" - offset: "0.0.0.128/25" - status__name: "Active" - """ - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[NextPrefixExtension, ChildPrefixExtension]) - object_creator.implement_design(design, commit=True) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.0/24").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.0/25").exists()) - self.assertTrue(Prefix.objects.filter(prefix="10.0.0.128/25").exists()) - - -class TestBGPExtension(TestCase): - """Test BGP extension.""" +@builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v1")) +class TestV1Extensions(TestCase): + """Test contrib extensions against Nautobot V1.""" def setUp(self): - try: - import nautobot_bgp_models.models # pylint:disable=import-outside-toplevel,unused-import,import-error # noqa: F401 - except ModuleNotFoundError: - self.skipTest("BGP Models app is not installed, skipping associated tests") + if nautobot_version >= "2.0.0": + self.skipTest("These tests are only supported in Nautobot 1.x") super().setUp() - def test_creation(self): - design_template = """ - sites: - - "!create_or_update:name": "Site" - status__name: "Active" - - device_roles: - - "!create_or_update:name": "test-role" - manufacturers: - - "!create_or_update:name": "test-manufacturer" +@builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v2")) +class TestV2Extensions(TestCase): + """Test contrib extensions against Nautobot V2.""" - device_types: - - manufacturer__name: "test-manufacturer" - "!create_or_update:model": "test-type" - - autonomous_systems: - - "!create_or_update:asn": 64500 - status__name: "Active" - - devices: - - "!create_or_update:name": "device1" - status__name: "Active" - site__name: "Site" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "Ethernet1/1" - type: "virtual" - status__name: "Active" - ip_addresses: - - "!create_or_update:address": "192.168.1.1/24" - status__name: "Active" - bgp_routing_instances: - - "!create_or_update:autonomous_system__asn": 64500 - "!ref": "device1-instance" - - - "!create_or_update:name": "device2" - status__name: "Active" - site__name: "Site" - device_role__name: "test-role" - device_type__model: "test-type" - interfaces: - - "!create_or_update:name": "Ethernet1/1" - type: "virtual" - status__name: "Active" - ip_addresses: - - "!create_or_update:address": "192.168.1.2/24" - status__name: "Active" - bgp_routing_instances: - - "!create_or_update:autonomous_system__asn": 64500 - "!ref": "device2-instance" - - bgp_peerings: - - "!bgp_peering": - endpoint_a: - "!create_or_update:routing_instance__device__name": "device1" - "!create_or_update:source_ip": - "!get:interface__device__name": "device1" - "!get:interface__name": "Ethernet1/1" - endpoint_z: - "!create_or_update:routing_instance__device__name": "device2" - "!create_or_update:source_ip": - "!get:interface__device__name": "device2" - "!get:interface__name": "Ethernet1/1" - status__name: "Active" - """ - from nautobot_bgp_models.models import Peering # pylint: disable=import-outside-toplevel - - design = yaml.safe_load(design_template) - object_creator = Builder(extensions=[BGPPeeringExtension]) - object_creator.implement_design(design, commit=True) - device1 = Device.objects.get(name="device1") - device2 = Device.objects.get(name="device2") - - endpoint1 = device1.bgp_routing_instances.first().endpoints.first() - endpoint2 = device2.bgp_routing_instances.first().endpoints.first() - self.assertEqual(endpoint1.peering, endpoint2.peering) - peering_pk = endpoint1.peering.pk - self.assertEqual(1, Peering.objects.all().count()) - self.assertEqual(endpoint2.peer, endpoint1) - self.assertEqual(endpoint1.peer, endpoint2) - - # confirm idempotence - object_creator.implement_design(design, commit=True) - self.assertEqual(1, Peering.objects.all().count()) - self.assertEqual(peering_pk, Peering.objects.first().pk) - self.assertEqual(endpoint2.peer, endpoint1) - self.assertEqual(endpoint1.peer, endpoint2) + def setUp(self): + if nautobot_version < "2.0.0": + self.skipTest("These tests are only supported in Nautobot 2.x") + super().setUp() diff --git a/nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml b/nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml new file mode 100644 index 00000000..a96dae99 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/lookup_extension.yaml @@ -0,0 +1,28 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.LookupExtension" + +designs: + - manufacturers: + - name: "Manufacturer1" + - name: "Manufacturer2" + device_types: + - "!lookup:manufacturer": + name: "Manufacturer1" + model: "model1" + - "!lookup:manufacturer:name": "Manufacturer2" + model: "model2" +checks: + - equal: + - model: "nautobot.dcim.models.DeviceType" + query: {model: "model1"} + attribute: "manufacturer" + - model: "nautobot.dcim.models.Manufacturer" + query: {name: "Manufacturer1"} + + - equal: + - model: "nautobot.dcim.models.DeviceType" + query: {model: "model2"} + attribute: "manufacturer" + - model: "nautobot.dcim.models.Manufacturer" + query: {name: "Manufacturer2"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml new file mode 100644 index 00000000..ea436b56 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/bgp_extension.yaml @@ -0,0 +1,78 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.BGPPeeringExtension" +designs: + - sites: + - "!create_or_update:name": "Site" + status__name: "Active" + + device_roles: + - "!create_or_update:name": "test-role" + + manufacturers: + - "!create_or_update:name": "test-manufacturer" + + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + + autonomous_systems: + - "!create_or_update:asn": 64500 + status__name: "Active" + + devices: + - "!create_or_update:name": "device1" + status__name: "Active" + site__name: "Site" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_addresses: + - "!create_or_update:address": "192.168.1.1/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device1-instance" + status__name: "Active" + + - "!create_or_update:name": "device2" + status__name: "Active" + site__name: "Site" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_addresses: + - "!create_or_update:address": "192.168.1.2/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device2-instance" + status__name: "Active" + + bgp_peerings: + - "!bgp_peering": + endpoint_a: + "!create_or_update:routing_instance__device__name": "device1" + "!create_or_update:source_ip": + "!get:interface__device__name": "device1" + "!get:interface__name": "Ethernet1/1" + endpoint_z: + "!create_or_update:routing_instance__device__name": "device2" + "!create_or_update:source_ip": + "!get:interface__device__name": "device2" + "!get:interface__name": "Ethernet1/1" + status__name: "Active" +checks: + - equal: + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device1"} + attribute: "peering" + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device2"} + attribute: "peering" diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml new file mode 100644 index 00000000..bd80c907 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/cable_connections.yaml @@ -0,0 +1,59 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.CableConnectionExtension" +designs: + - sites: + - "!create_or_update:name": "Site" + status__name: "Active" + device_roles: + - "!create_or_update:name": "test-role" + manufacturers: + - "!create_or_update:name": "test-manufacturer" + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + site__name: "Site" + status__name: "Active" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + - "!create_or_update:name": "Device 2" + site__name: "Site" + status__name: "Active" + device_role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + # Second design, same as the first, checks for + # cable connection idempotence + - devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + - "!create_or_update:name": "Device 2" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + +checks: + - connected: + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 1", name: "GigabitEthernet1"} + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 2", name: "GigabitEthernet1"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml new file mode 100644 index 00000000..0f07d112 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/child_prefix.yaml @@ -0,0 +1,43 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" + - "nautobot_design_builder.contrib.ext.ChildPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + - name: "Servers" + prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + length: 24 + status__name: "Active" + "!ref": "parent_prefix" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.0/25" + status__name: "Active" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.128/25" + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/25"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.128/25"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml new file mode 100644 index 00000000..937c9bc1 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix.yaml @@ -0,0 +1,55 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + - name: "Servers" + prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.1.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.3.0/24"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml new file mode 100644 index 00000000..2a4023c2 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v1/next_prefix_by_role_and_tenant.yaml @@ -0,0 +1,27 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + - name: "Servers" + prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + role__name: "Video" + tenant__name: "Nautobot Airports" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml new file mode 100644 index 00000000..627df53e --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/bgp_extension.yaml @@ -0,0 +1,87 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.BGPPeeringExtension" +designs: + - roles: + - name: "test-role" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + prefixes: + - status__name: "Active" + prefix: "192.168.1.0/24" + + location_types: + - name: "Site" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + locations: + name: "Site" + status__name: "Active" + + manufacturers: + - "!create_or_update:name": "test-manufacturer" + + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + + autonomous_systems: + - "!create_or_update:asn": 64500 + status__name: "Active" + + devices: + - "!create_or_update:name": "device1" + status__name: "Active" + location__name: "Site" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_address_assignments: + - ip_address: + "!create_or_update:address": "192.168.1.1/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device1-instance" + status__name: "Active" + + - "!create_or_update:name": "device2" + status__name: "Active" + location__name: "Site" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "Ethernet1/1" + type: "virtual" + status__name: "Active" + ip_address_assignments: + - ip_address: + "!create_or_update:address": "192.168.1.2/24" + status__name: "Active" + bgp_routing_instances: + - "!create_or_update:autonomous_system__asn": 64500 + "!ref": "device2-instance" + status__name: "Active" + + bgp_peerings: + - "!bgp_peering": + endpoint_a: + "!create_or_update:routing_instance__device__name": "device1" + "!create_or_update:source_ip__address": "192.168.1.1/24" + endpoint_z: + "!create_or_update:routing_instance__device__name": "device2" + "!create_or_update:source_ip__address": "192.168.1.2/24" + status__name: "Active" +checks: + - equal: + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device1"} + attribute: "peering" + - model: "nautobot_bgp_models.models.PeerEndpoint" + query: {routing_instance__device__name: "device2"} + attribute: "peering" diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml new file mode 100644 index 00000000..f7aef4ed --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/cable_connections.yaml @@ -0,0 +1,68 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.CableConnectionExtension" +designs: + - location_types: + - "!create_or_update:name": "Site" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + locations: + - location_type__name: "Site" + "!create_or_update:name": "Site" + status__name: "Active" + roles: + - "!create_or_update:name": "test-role" + content_types: + - "!get:app_label": "dcim" + "!get:model": "device" + manufacturers: + - "!create_or_update:name": "test-manufacturer" + device_types: + - manufacturer__name: "test-manufacturer" + "!create_or_update:model": "test-type" + devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + location__name: "Site" + status__name: "Active" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + - "!create_or_update:name": "Device 2" + location__name: "Site" + status__name: "Active" + role__name: "test-role" + device_type__model: "test-type" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + type: "1000base-t" + status__name: "Active" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + # Second design, same as the first, checks for + # cable connection idempotence + - devices: + - "!create_or_update:name": "Device 1" + "!ref": "device1" + - "!create_or_update:name": "Device 2" + interfaces: + - "!create_or_update:name": "GigabitEthernet1" + "!connect_cable": + status__name: "Planned" + to: + device: "!ref:device1" + name: "GigabitEthernet1" + +checks: + - connected: + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 1", name: "GigabitEthernet1"} + - model: "nautobot.dcim.models.Interface" + query: {device__name: "Device 2", name: "GigabitEthernet1"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml new file mode 100644 index 00000000..d4ed1d17 --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/child_prefix.yaml @@ -0,0 +1,49 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" + - "nautobot_design_builder.contrib.ext.ChildPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - name: "Servers" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + length: 24 + status__name: "Active" + "!ref": "parent_prefix" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.0/25" + status__name: "Active" + - "!child_prefix": + parent: "!ref:parent_prefix" + offset: "0.0.0.128/25" + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/25"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.128/25"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml new file mode 100644 index 00000000..bceb228c --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix.yaml @@ -0,0 +1,61 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - name: "Servers" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" + - "!next_prefix": + prefix: + - "10.0.0.0/23" + - "10.0.2.0/23" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.0.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.1.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.3.0/24"} diff --git a/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml new file mode 100644 index 00000000..40faf41f --- /dev/null +++ b/nautobot_design_builder/contrib/tests/testdata/nautobot_v2/next_prefix_by_role_and_tenant.yaml @@ -0,0 +1,33 @@ +--- +extensions: + - "nautobot_design_builder.contrib.ext.NextPrefixExtension" +designs: + - tenants: + - name: "Nautobot Airports" + roles: + - name: "Video" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - name: "Servers" + content_types: + - "!get:app_label": "ipam" + "!get:model": "prefix" + - prefixes: + - prefix: "10.0.0.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Servers" + - prefix: "10.0.2.0/23" + status__name: "Active" + tenant__name: "Nautobot Airports" + role__name: "Video" + - "!next_prefix": + role__name: "Video" + tenant__name: "Nautobot Airports" + length: 24 + status__name: "Active" +checks: + - model_exists: + model: "nautobot.ipam.models.Prefix" + query: {prefix: "10.0.2.0/24"} diff --git a/nautobot_design_builder/tests/test_builder.py b/nautobot_design_builder/tests/test_builder.py index 8a26cafd..207dc5d5 100644 --- a/nautobot_design_builder/tests/test_builder.py +++ b/nautobot_design_builder/tests/test_builder.py @@ -138,12 +138,12 @@ def test_runner(self, roll_back: Mock): @builder_test_case(os.path.join(os.path.dirname(__file__), "testdata")) -class TestGeneralDesigns(TestCase): # pylint:disable=too-many-public-methods +class TestGeneralDesigns(TestCase): """Designs that should work with all versions of Nautobot.""" @builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v1")) -class TestV1Designs(TestCase): # pylint:disable=too-many-public-methods +class TestV1Designs(TestCase): """Designs that only work in Nautobot 1.x""" def setUp(self): @@ -153,7 +153,7 @@ def setUp(self): @builder_test_case(os.path.join(os.path.dirname(__file__), "testdata", "nautobot_v2")) -class TestV2Designs(TestCase): # pylint:disable=too-many-public-methods +class TestV2Designs(TestCase): """Designs that only work in Nautobot 1.x""" def setUp(self):