Beispiel #1
0
    def test_str(self):
        """Validate that the str() method works correctly."""
        associations = [
            RelationshipAssociation(relationship=self.o2o_1,
                                    source=self.racks[0],
                                    destination=self.sites[1]),
            RelationshipAssociation(relationship=self.o2os_1,
                                    source=self.racks[0],
                                    destination=self.racks[1]),
        ]
        for association in associations:
            association.validated_save()

        self.assertEqual(str(associations[0]),
                         f"{self.racks[0]} -> {self.sites[1]} - {self.o2o_1}")
        self.assertEqual(
            str(associations[1]),
            f"{self.racks[0]} <-> {self.racks[1]} - {self.o2os_1}")
        self.assertEqual(
            str(self.invalid_relationship_associations[0]),
            f"{self.sites[1]} -> unknown - {self.invalid_relationships[0]}",
        )
        self.assertEqual(
            str(self.invalid_relationship_associations[1]),
            f"unknown -> {self.sites[1]} - {self.invalid_relationships[1]}",
        )
        self.assertEqual(
            str(self.invalid_relationship_associations[2]),
            f"unknown <-> unknown - {self.invalid_relationships[2]}",
        )
Beispiel #2
0
    def setUp(self):
        super().setUp()

        self.invalid_object_pks = [
            uuid.uuid4(),
            uuid.uuid4(),
        ]

        self.invalid_relationship_associations = [
            RelationshipAssociation(
                relationship=self.invalid_relationships[0],
                source=self.sites[1],
                destination_type=self.invalid_ct,
                destination_id=self.invalid_object_pks[1],
            ),
            RelationshipAssociation(
                relationship=self.invalid_relationships[1],
                source_type=self.invalid_ct,
                source_id=self.invalid_object_pks[0],
                destination=self.sites[1],
            ),
            RelationshipAssociation(
                relationship=self.invalid_relationships[2],
                source_type=self.invalid_ct,
                source_id=self.invalid_object_pks[0],
                destination_type=self.invalid_ct,
                destination_id=self.invalid_object_pks[1],
            ),
        ]
        for cra in self.invalid_relationship_associations:
            cra.validated_save()
Beispiel #3
0
    def test_delete_cascade(self):
        """Verify that a RelationshipAssociation is deleted if either of the associated records is deleted."""
        initial_count = RelationshipAssociation.objects.count()
        associations = [
            RelationshipAssociation(relationship=self.m2m_1,
                                    source=self.racks[0],
                                    destination=self.vlans[0]),
            RelationshipAssociation(relationship=self.m2m_1,
                                    source=self.racks[0],
                                    destination=self.vlans[1]),
            RelationshipAssociation(relationship=self.m2m_1,
                                    source=self.racks[1],
                                    destination=self.vlans[0]),
            # Create an association loop just to make sure it works correctly on deletion
            RelationshipAssociation(relationship=self.o2o_2,
                                    source=self.sites[2],
                                    destination=self.sites[3]),
            RelationshipAssociation(relationship=self.o2o_2,
                                    source=self.sites[3],
                                    destination=self.sites[2]),
        ]
        for association in associations:
            association.validated_save()
        # Create a self-referential association as well; validated_save() would correctly reject this one as invalid
        RelationshipAssociation.objects.create(relationship=self.o2o_2,
                                               source=self.sites[4],
                                               destination=self.sites[4])

        self.assertEqual(6 + initial_count,
                         RelationshipAssociation.objects.count())

        # Test automatic deletion of RelationshipAssociations when their 'source' object is deleted
        self.racks[0].delete()

        # Both relations involving racks[0] should have been deleted
        # The relation between racks[1] and vlans[0] should remain, as should the site relations
        self.assertEqual(4 + initial_count,
                         RelationshipAssociation.objects.count())

        # Test automatic deletion of RelationshipAssociations when their 'destination' object is deleted
        self.vlans[0].delete()

        # Site relation remains
        self.assertEqual(3 + initial_count,
                         RelationshipAssociation.objects.count())

        # Test automatic deletion of RelationshipAssociations when there's a loop of source/destination references
        self.sites[3].delete()
        self.assertEqual(1 + initial_count,
                         RelationshipAssociation.objects.count())

        # Test automatic deletion of RelationshipAssociations when the same object is both source and destination
        self.sites[4].delete()
        self.assertEqual(initial_count,
                         RelationshipAssociation.objects.count())
Beispiel #4
0
    def test_clean_wrong_type(self):
        # Create with the wrong source Type
        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.m2m_1,
                                          source=self.sites[0],
                                          destination=self.vlans[0])
            cra.clean()
        expected_errors = {
            "source_type":
            ["source_type has a different value than defined in Vlan to Rack"]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        # Create with the wrong destination Type
        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.m2m_1,
                                          source=self.racks[0],
                                          destination=self.racks[0])
            cra.clean()
        expected_errors = {
            "destination_type": [
                "destination_type has a different value than defined in Vlan to Rack"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)
Beispiel #5
0
    def test_create_relationship_associations_invalid_1(self):
        """
        A new record CANNOT create ONE_TO_ONE relations where its "destination" is already associated.
        """
        # Existing ONE_TO_ONE relation
        RelationshipAssociation(
            relationship=self.relationship_1,
            source_type=self.relationship_1.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_1.destination_type,
            destination_id=self.ipaddress_1.pk,
        ).validated_save()

        # Can't associate New Device with IP Address 1 (already associated to Device 1)
        form = DeviceForm(data=dict(
            **self.device_form_base_data, **{
                f"cr_{self.relationship_1.slug}__destination":
                self.ipaddress_1.pk
            }))
        self.assertFalse(form.is_valid())
        self.assertEqual(
            "10.1.1.1/24 is already involved in a BGP Router-ID relationship",
            form.errors[f"cr_{self.relationship_1.slug}__destination"][0],
        )

        # Can't associate new IP address with Device 1 (already associated with IP Address 1)
        form = IPAddressForm(data=dict(
            **self.ipaddress_form_base_data, **
            {f"cr_{self.relationship_1.slug}__source": self.device_1.pk}))
        self.assertFalse(form.is_valid())
        self.assertEqual(
            "Device 1 is already involved in a BGP Router-ID relationship",
            form.errors[f"cr_{self.relationship_1.slug}__source"][0],
        )
Beispiel #6
0
    def test_create_relationship_associations_invalid_2(self):
        """
        A new record CANNOT create ONE_TO_MANY relations where any of its "destinations" are already associated.
        """
        # Existing ONE_TO_MANY relation
        RelationshipAssociation(
            relationship=self.relationship_2,
            source_type=self.relationship_2.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_2.destination_type,
            destination_id=self.vlangroup_1.pk,
        ).validated_save()

        # Can't associate New Device with VLAN Group 1 (already associated to Device 1)
        form = DeviceForm(data=dict(
            **self.device_form_base_data,
            **{
                f"cr_{self.relationship_2.slug}__destination":
                [self.vlangroup_1.pk, self.vlangroup_2.pk]
            },
        ))
        self.assertFalse(form.is_valid())
        self.assertEqual(
            "VLAN Group 1 is already involved in a Device VLAN Groups relationship",
            form.errors[f"cr_{self.relationship_2.slug}__destination"][0],
        )
Beispiel #7
0
    def test_create_relationship_associations_invalid_3(self):
        """
        A new record CANNOT create ONE_TO_ONE_SYMMETRIC relations where its peer is already associated.
        """
        # Existing ONE_TO_ONE_SYMMETRIC relation
        RelationshipAssociation(
            relationship=self.relationship_3,
            source_type=self.relationship_3.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_3.destination_type,
            destination_id=self.device_2.pk,
        ).validated_save()

        # Peer is already a source for this relationship
        form = DeviceForm(data=dict(
            **self.device_form_base_data, **
            {f"cr_{self.relationship_3.slug}__peer": self.device_1.pk}))
        self.assertFalse(form.is_valid())
        self.assertEqual(
            "Device 1 is already involved in a HA Device Peer relationship",
            form.errors[f"cr_{self.relationship_3.slug}__peer"][0],
        )

        # Peer is already a destination for this relationship
        form = DeviceForm(data=dict(
            **self.device_form_base_data, **
            {f"cr_{self.relationship_3.slug}__peer": self.device_2.pk}))
        self.assertFalse(form.is_valid())
        self.assertEqual(
            "Device 2 is already involved in a HA Device Peer relationship",
            form.errors[f"cr_{self.relationship_3.slug}__peer"][0],
        )
Beispiel #8
0
    def test_update_relationship_associations_valid_2(self):
        """
        An existing record with an existing ONE_TO_ONE association can change its source.
        """
        # Existing ONE_TO_ONE relation
        RelationshipAssociation(
            relationship=self.relationship_1,
            source_type=self.relationship_1.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_1.destination_type,
            destination_id=self.ipaddress_1.pk,
        ).validated_save()

        form = IPAddressForm(
            instance=self.ipaddress_1,
            data={
                "address": self.ipaddress_1.address,
                "status": self.status_active,
                f"cr_{self.relationship_1.slug}__source": self.device_2.pk,
            },
        )
        self.assertTrue(form.is_valid(), form.errors)
        self.assertTrue(form.save())

        # Existing ONE_TO_ONE relation should have been deleted and replaced
        with self.assertRaises(RelationshipAssociation.DoesNotExist):
            RelationshipAssociation.objects.get(
                relationship=self.relationship_1, source_id=self.device_1.pk)
        RelationshipAssociation.objects.get(relationship=self.relationship_1,
                                            source_id=self.device_2.pk,
                                            destination_id=self.ipaddress_1.pk)
Beispiel #9
0
    def test_update_relationship_associations_valid_3(self):
        """
        An existing record with an existing ONE_TO_MANY association can change its source.
        """
        # Existing ONE_TO_MANY relation
        RelationshipAssociation(
            relationship=self.relationship_2,
            source_type=self.relationship_2.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_2.destination_type,
            destination_id=self.vlangroup_1.pk,
        ).validated_save()

        form = VLANGroupForm(
            instance=self.vlangroup_1,
            data={
                "name": self.vlangroup_1.name,
                "slug": self.vlangroup_1.slug,
                "site": self.site,
                f"cr_{self.relationship_2.slug}__source": self.device_2.pk,
            },
        )
        self.assertTrue(form.is_valid(), form.errors)
        self.assertTrue(form.save())

        # Existing ONE_TO_MANY relation should have been deleted and replaced
        with self.assertRaises(RelationshipAssociation.DoesNotExist):
            RelationshipAssociation.objects.get(
                relationship=self.relationship_2, source_id=self.device_1.pk)
        RelationshipAssociation.objects.get(relationship=self.relationship_2,
                                            source_id=self.device_2.pk,
                                            destination_id=self.vlangroup_1.pk)
Beispiel #10
0
    def test_clean_wrong_type(self):
        # Create with the wrong source Type
        with self.assertRaises(ValidationError):
            cra = RelationshipAssociation(relationship=self.m2m_1, source=self.sites[0], destination=self.vlans[0])
            cra.clean()

        # Create with the wrong destination Type
        with self.assertRaises(ValidationError):
            cra = RelationshipAssociation(relationship=self.m2m_1, source=self.racks[0], destination=self.racks[0])
            cra.clean()
Beispiel #11
0
 def test_relationship_association_validator_raises_exception(self):
     status_active = Status.objects.create(name="status1", slug="status1")
     prefix = Prefix.objects.create(
         prefix=netaddr.IPNetwork("192.168.10.0/24"))
     ipaddress = IPAddress.objects.create(address="192.168.22.1/24",
                                          status=status_active)
     relationship = Relationship.objects.create(
         name="Test Relationship",
         slug="test-relationship",
         source_type=ContentType.objects.get_for_model(Prefix),
         destination_type=ContentType.objects.get_for_model(IPAddress),
         type=RelationshipTypeChoices.TYPE_ONE_TO_MANY,
     )
     relationship_assoc = RelationshipAssociation(relationship=relationship,
                                                  source=prefix,
                                                  destination=ipaddress)
     with self.assertRaises(ValidationError):
         relationship_assoc.clean()
Beispiel #12
0
    def test_get_peer(self):
        cra = RelationshipAssociation(relationship=self.m2m_1, source=self.racks[0], destination=self.vlans[0])
        cra.save()

        self.assertEqual(cra.get_peer(self.racks[0]), self.vlans[0])
        self.assertEqual(cra.get_peer(self.vlans[0]), self.racks[0])
        self.assertEqual(cra.get_peer(self.vlans[1]), None)
Beispiel #13
0
    def test_create_invalid_relationship_association(self):
        """Test creation of invalid relationship association restricted by destination/source filter."""

        relationship = Relationship.objects.create(
            name="Site to Rack Rel 1",
            slug="site-to-rack-rel-1",
            source_type=self.site_ct,
            source_filter={"name": [self.sites[0].name]},
            destination_type=self.rack_ct,
            destination_label="Primary Rack",
            type=RelationshipTypeChoices.TYPE_ONE_TO_ONE,
            destination_filter={"name": [self.racks[0].name]},
        )

        associations = (
            (
                "source",
                RelationshipAssociation(relationship=relationship,
                                        source=self.sites[1],
                                        destination=self.racks[0]),
            ),
            (
                "destination",
                RelationshipAssociation(relationship=relationship,
                                        source=self.sites[0],
                                        destination=self.racks[1]),
            ),
        )

        for side_name, association in associations:
            side = getattr(association, side_name)
            with self.assertRaises(ValidationError) as handler:
                association.validated_save()
            expected_errors = {
                side_name: [
                    f"{side} violates {relationship} {side_name}_filter restriction"
                ]
            }
            self.assertEqual(handler.exception.message_dict, expected_errors)
Beispiel #14
0
    def test_get_peer(self):
        """Validate that the get_peer() method works correctly."""
        cra = RelationshipAssociation(relationship=self.m2m_1,
                                      source=self.racks[0],
                                      destination=self.vlans[0])
        cra.validated_save()

        self.assertEqual(cra.get_peer(self.racks[0]), self.vlans[0])
        self.assertEqual(cra.get_peer(self.vlans[0]), self.racks[0])
        self.assertEqual(cra.get_peer(self.vlans[1]), None)
Beispiel #15
0
    def test_update_relationship_associations_valid_4(self):
        """
        An existing record with an existing ONE_TO_ONE_SYMMETRIC association can change its peer.

        This differs from test_update_relationship_associations_valid_1 in that the existing association has this
        record as the destination rather than the source, which *should* work either way.
        """
        # Existing ONE_TO_ONE_SYMMETRIC relation
        RelationshipAssociation(
            relationship=self.relationship_3,
            source_type=self.relationship_3.source_type,
            source_id=self.device_3.pk,
            destination_type=self.relationship_3.destination_type,
            destination_id=self.device_1.pk,
        ).validated_save()

        form = DeviceForm(
            instance=self.device_1,
            data={
                "site": self.site,
                "device_role": self.device_role,
                "device_type": self.device_type,
                "status": self.status_active,
                f"cr_{self.relationship_3.slug}__peer": self.device_2.pk,
            },
        )
        self.assertTrue(form.is_valid(), form.errors)
        self.assertTrue(form.save())

        # Existing ONE_TO_ONE_SYMMETRIC relation should have been deleted and replaced
        with self.assertRaises(RelationshipAssociation.DoesNotExist):
            RelationshipAssociation.objects.get(
                relationship=self.relationship_3, source_id=self.device_3.pk)
        RelationshipAssociation.objects.get(
            Q(source_id=self.device_1.pk, destination_id=self.device_2.pk)
            | Q(source_id=self.device_2.pk, destination_id=self.device_1.pk),
            relationship=self.relationship_3,
        )
Beispiel #16
0
    def test_clean_check_quantity_m2m(self):
        """Validate that many-to-many relationship can have many relationship associations."""
        cra = RelationshipAssociation(relationship=self.m2m_1, source=self.racks[0], destination=self.vlans[0])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.m2m_1, source=self.racks[0], destination=self.vlans[1])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.m2m_1, source=self.racks[1], destination=self.vlans[2])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.m2m_1, source=self.racks[2], destination=self.vlans[0])
        cra.clean()
Beispiel #17
0
    def test_clean_check_quantity_o2m(self):
        """Validate that one-to-many relationships can't have more than one relationship association per source. """

        cra = RelationshipAssociation(relationship=self.o2m_1, source=self.sites[0], destination=self.vlans[0])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.o2m_1, source=self.sites[0], destination=self.vlans[1])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.o2m_1, source=self.sites[1], destination=self.vlans[2])
        cra.clean()
        cra.save()

        with self.assertRaises(ValidationError):
            cra = RelationshipAssociation(relationship=self.o2o_1, source=self.sites[2], destination=self.vlans[0])
            cra.clean()
Beispiel #18
0
    def setUpTestData(cls):
        cls.device_type = ContentType.objects.get_for_model(Device)
        cls.vlan_type = ContentType.objects.get_for_model(VLAN)

        cls.relationships = (
            Relationship(
                name="Device VLANs",
                slug="device-vlans",
                type="many-to-many",
                source_type=cls.device_type,
                destination_type=cls.vlan_type,
            ),
            Relationship(
                name="Primary VLAN",
                slug="primary-vlan",
                type="one-to-many",
                source_type=cls.vlan_type,
                destination_type=cls.device_type,
            ),
        )
        for relationship in cls.relationships:
            relationship.validated_save()

        manufacturer = Manufacturer.objects.create(name="Manufacturer 1", slug="manufacturer-1")
        devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1", slug="device-type-1")
        devicerole = DeviceRole.objects.create(name="Device Role 1", slug="device-role-1")
        site = Site.objects.create(name="Site 1", slug="site-1")
        cls.devices = (
            Device.objects.create(name="Device 1", device_type=devicetype, device_role=devicerole, site=site),
            Device.objects.create(name="Device 2", device_type=devicetype, device_role=devicerole, site=site),
        )
        cls.vlans = (
            VLAN.objects.create(vid=1, name="VLAN 1"),
            VLAN.objects.create(vid=2, name="VLAN 2"),
        )

        RelationshipAssociation(
            relationship=cls.relationships[0],
            source_type=cls.device_type,
            source_id=cls.devices[0].pk,
            destination_type=cls.vlan_type,
            destination_id=cls.vlans[0].pk,
        ).validated_save()
        RelationshipAssociation(
            relationship=cls.relationships[0],
            source_type=cls.device_type,
            source_id=cls.devices[1].pk,
            destination_type=cls.vlan_type,
            destination_id=cls.vlans[1].pk,
        ).validated_save()
        RelationshipAssociation(
            relationship=cls.relationships[1],
            source_type=cls.vlan_type,
            source_id=cls.vlans[0].pk,
            destination_type=cls.device_type,
            destination_id=cls.devices[0].pk,
        ).validated_save()
        RelationshipAssociation(
            relationship=cls.relationships[1],
            source_type=cls.vlan_type,
            source_id=cls.vlans[1].pk,
            destination_type=cls.device_type,
            destination_id=cls.devices[1].pk,
        ).validated_save()
Beispiel #19
0
    def test_get_relationships_data(self):
        # In addition to the invalid associations for sites[1] defined in self.setUp(), add some valid ones
        associations = [
            RelationshipAssociation(relationship=self.o2m_1,
                                    source=self.sites[1],
                                    destination=self.vlans[0]),
            RelationshipAssociation(relationship=self.o2o_1,
                                    source=self.racks[0],
                                    destination=self.sites[1]),
            RelationshipAssociation(relationship=self.o2o_2,
                                    source=self.sites[0],
                                    destination=self.sites[1]),
        ]
        for association in associations:
            association.validated_save()

        with self.assertLogs(logger=logging.getLogger(
                "nautobot.extras.models.relationships"),
                             level="ERROR"):
            data = self.sites[1].get_relationships_data()
        self.maxDiff = None
        # assertEqual doesn't work well on the entire data at once because it includes things like queryset objects
        self.assertEqual(sorted(data.keys()),
                         ["destination", "peer", "source"])
        self.assertEqual(
            set(data["destination"].keys()),
            {self.o2o_1, self.o2o_2, self.invalid_relationships[1]})
        self.assertEqual(
            data["destination"][self.o2o_1],
            {
                "has_many": False,
                "label": "Primary Rack",
                "peer_type": self.rack_ct,
                "url": reverse("dcim:rack", kwargs={"pk": self.racks[0].pk}),
                "value": self.racks[0],
            },
        )
        self.assertEqual(
            data["destination"][self.o2o_2],
            {
                "has_many": False,
                "label": "Alphabetically Subsequent",
                "peer_type": self.site_ct,
                "url": reverse("dcim:site",
                               kwargs={"slug": self.sites[0].slug}),
                "value": self.sites[0],
            },
        )
        self.assertEqual(
            data["destination"][self.invalid_relationships[1]],
            {
                "has_many": False,
                "label": "Invalid Relationship 2",
                "peer_type": self.invalid_ct,
                "url": None,
                "value": None,
            },
        )
        self.assertEqual(set(data["peer"].keys()), {self.m2ms_1})
        # Peer queryset is complex, but evaluates to an empty list in this case
        self.assertEqual(list(data["peer"][self.m2ms_1]["queryset"]), [])
        del data["peer"][self.m2ms_1]["queryset"]
        self.assertEqual(
            data["peer"][self.m2ms_1],
            {
                "has_many": True,
                "label": "sites",
                "peer_type": self.site_ct,
                "value": None,
            },
        )
        self.assertEqual(
            set(data["source"].keys()),
            {self.o2m_1, self.o2o_2, self.invalid_relationships[0]})
        self.assertEqual(list(data["source"][self.o2m_1]["queryset"]),
                         [associations[0]])
        del data["source"][self.o2m_1]["queryset"]
        self.assertEqual(
            data["source"][self.o2m_1],
            {
                "has_many": True,
                "label": "VLANs",
                "peer_type": self.vlan_ct,
                "value": None,
            },
        )
        self.assertEqual(
            data["source"][self.o2o_2],
            {
                "has_many": False,
                "label": "Alphabetically Prior",
                "peer_type": self.site_ct,
                "url": None,
                "value": None,
            },
        )
        self.assertEqual(
            data["source"][self.invalid_relationships[0]],
            {
                "has_many": False,
                "label": "Invalid Relationship 1",
                "peer_type": self.invalid_ct,
                "url": None,
                # value is None because the related object can't actually be found
                "value": None,
            },
        )
Beispiel #20
0
    def test_exception_not_raised_when_updating_instance_with_relationship_type_o2o_or_o2m(
            self):
        """Validate 'Unable to create more than one relationship-association...' not raise when updating instance with
        type one-to-one, symmetric-one-to-one, one-to-many relationship."""

        # Assert Exception not raise updating source of RelationshipAssociation with one-to-many relationship type
        cra_1 = RelationshipAssociation(relationship=self.o2m_1,
                                        source=self.sites[0],
                                        destination=self.vlans[1])
        cra_1.validated_save()

        cra_1.source = self.sites[1]
        cra_1.validated_save()

        self.assertEqual(cra_1.source, self.sites[1])

        # Validate Exception not raised when calling .validated_save() on a RelationshipAssociation instance without making any update
        cra_1.validated_save()

        # Assert Exception not raise updating source of RelationshipAssociation with one-to-one relationship type
        cra_2 = RelationshipAssociation(relationship=self.o2o_1,
                                        source=self.racks[0],
                                        destination=self.sites[0])
        cra_2.validated_save()

        cra_2.source = self.racks[1]
        cra_2.validated_save()

        self.assertEqual(cra_2.source, self.racks[1])

        # Assert Exception not raise updating destination of RelationshipAssociation with one-to-one relationship type
        cra_3 = RelationshipAssociation(relationship=self.o2o_1,
                                        source=self.racks[2],
                                        destination=self.sites[2])
        cra_3.validated_save()

        cra_3.destination = self.sites[4]
        cra_3.validated_save()

        self.assertEqual(cra_3.destination, self.sites[4])

        # Assert Exception not raise updating destination of RelationshipAssociation with symmetric-one-to-one relationship type
        cra_4 = RelationshipAssociation(relationship=self.o2os_1,
                                        source=self.racks[0],
                                        destination=self.racks[2])
        cra_4.validated_save()

        cra_4.destination = self.racks[1]
        cra_4.validated_save()

        self.assertEqual(cra_4.destination, self.racks[1])
    def test_clean_check_quantity_o2o(self):
        """Validate that one-to-one relationships can't have more than one relationship association per side."""

        cra = RelationshipAssociation(relationship=self.o2o_1,
                                      source=self.racks[0],
                                      destination=self.sites[0])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.o2o_1,
                                      source=self.racks[1],
                                      destination=self.sites[1])
        cra.clean()
        cra.save()

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2o_1,
                                          source=self.racks[0],
                                          destination=self.sites[2])
            cra.clean()

        expected_errors = {
            "source": [
                "Unable to create more than one Primary Rack per Site association to Rack A (source)"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2o_1,
                                          source=self.racks[2],
                                          destination=self.sites[0])
            cra.clean()
        expected_errors = {
            "destination": [
                "Unable to create more than one Primary Rack per Site association to Site A (destination)"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)
Beispiel #22
0
    def test_update_relationship_associations_valid_1(self):
        """
        An existing record with an existing ONE_TO_ONE or ONE_TO_MANY association can change its destination(s).
        """
        # Existing ONE_TO_ONE relation
        RelationshipAssociation(
            relationship=self.relationship_1,
            source_type=self.relationship_1.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_1.destination_type,
            destination_id=self.ipaddress_1.pk,
        ).validated_save()
        # Existing ONE_TO_MANY relation
        RelationshipAssociation(
            relationship=self.relationship_2,
            source_type=self.relationship_2.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_2.destination_type,
            destination_id=self.vlangroup_1.pk,
        ).validated_save()
        # Existing ONE_TO_ONE_SYMMETRIC relation
        RelationshipAssociation(
            relationship=self.relationship_3,
            source_type=self.relationship_3.source_type,
            source_id=self.device_1.pk,
            destination_type=self.relationship_3.destination_type,
            destination_id=self.device_3.pk,
        ).validated_save()

        form = DeviceForm(
            instance=self.device_1,
            data={
                "site":
                self.site,
                "device_role":
                self.device_role,
                "device_type":
                self.device_type,
                "status":
                self.status_active,
                f"cr_{self.relationship_1.slug}__destination":
                self.ipaddress_2.pk,
                f"cr_{self.relationship_2.slug}__destination":
                [self.vlangroup_2.pk],
                f"cr_{self.relationship_3.slug}__peer":
                self.device_2.pk,
            },
        )
        self.assertTrue(form.is_valid(), form.errors)
        self.assertTrue(form.save())

        # Existing ONE_TO_ONE relation should have been deleted and replaced
        with self.assertRaises(RelationshipAssociation.DoesNotExist):
            RelationshipAssociation.objects.get(
                relationship=self.relationship_1,
                destination_id=self.ipaddress_1.pk)
        RelationshipAssociation.objects.get(relationship=self.relationship_1,
                                            source_id=self.device_1.pk,
                                            destination_id=self.ipaddress_2.pk)

        # Existing ONE_TO_MANY relation should have been deleted and replaced
        with self.assertRaises(RelationshipAssociation.DoesNotExist):
            RelationshipAssociation.objects.get(
                relationship=self.relationship_2,
                destination_id=self.vlangroup_1.pk)
        RelationshipAssociation.objects.get(relationship=self.relationship_2,
                                            source_id=self.device_1.pk,
                                            destination_id=self.vlangroup_2.pk)

        # Existing ONE_TO_ONE_SYMMETRIC relation should have been deleted and replaced
        with self.assertRaises(RelationshipAssociation.DoesNotExist):
            RelationshipAssociation.objects.get(
                relationship=self.relationship_3,
                destination_id=self.device_3.pk)
        RelationshipAssociation.objects.get(
            Q(source_id=self.device_1.pk, destination_id=self.device_2.pk)
            | Q(source_id=self.device_2.pk, destination_id=self.device_1.pk),
            relationship=self.relationship_3,
        )
    def test_clean_check_quantity_o2m(self):
        """Validate that one-to-many relationships can't have more than one relationship association per source."""

        cra = RelationshipAssociation(relationship=self.o2m_1,
                                      source=self.sites[0],
                                      destination=self.vlans[0])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.o2m_1,
                                      source=self.sites[0],
                                      destination=self.vlans[1])
        cra.clean()
        cra.save()

        cra = RelationshipAssociation(relationship=self.o2m_1,
                                      source=self.sites[1],
                                      destination=self.vlans[2])
        cra.clean()
        cra.save()

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2m_1,
                                          source=self.sites[2],
                                          destination=self.vlans[0])
            cra.clean()
        expected_errors = {
            "destination": [
                "Unable to create more than one generic site to vlan association to VLAN A (100) (destination)",
            ],
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)
Beispiel #24
0
    def test_clean_check_quantity_o2o(self):
        """Validate that one-to-one relationships can't have more than one relationship association per side."""

        cra = RelationshipAssociation(relationship=self.o2o_1,
                                      source=self.racks[0],
                                      destination=self.sites[0])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.o2o_1,
                                      source=self.racks[1],
                                      destination=self.sites[1])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.o2os_1,
                                      source=self.racks[0],
                                      destination=self.racks[1])
        cra.validated_save()

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2o_1,
                                          source=self.racks[0],
                                          destination=self.sites[2])
            cra.clean()

        expected_errors = {
            "source": [
                "Unable to create more than one Primary Rack per Site association to Rack A (source)"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2o_1,
                                          source=self.racks[2],
                                          destination=self.sites[0])
            cra.clean()
        expected_errors = {
            "destination": [
                "Unable to create more than one Primary Rack per Site association to Site A (destination)"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2os_1,
                                          source=self.racks[0],
                                          destination=self.racks[2])
            cra.clean()
        expected_errors = {
            "source": [
                "Unable to create more than one Redundant Rack association to Rack A (source)"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        # Slightly tricky case - a symmetric one-to-one relationship where the proposed *source* is already in use
        # as a *destination* in a different RelationshipAssociation
        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2os_1,
                                          source=self.racks[1],
                                          destination=self.racks[2])
            cra.clean()
        expected_errors = {
            "source": [
                "Unable to create more than one Redundant Rack association involving Rack B (peer)"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)
Beispiel #25
0
    def test_generic_relation(self):
        """Verify that the GenericRelations on the involved models work correctly."""
        associations = (
            RelationshipAssociation(relationship=self.m2m_1,
                                    source=self.racks[0],
                                    destination=self.vlans[0]),
            RelationshipAssociation(relationship=self.m2m_1,
                                    source=self.racks[0],
                                    destination=self.vlans[1]),
            RelationshipAssociation(relationship=self.o2o_1,
                                    source=self.racks[0],
                                    destination=self.sites[0]),
        )
        for association in associations:
            association.validated_save()

        # Check that the GenericRelation lookup works correctly
        self.assertEqual(3, self.racks[0].source_for_associations.count())
        self.assertEqual(0, self.racks[0].destination_for_associations.count())
        self.assertEqual(0, self.vlans[0].source_for_associations.count())
        self.assertEqual(1, self.vlans[0].destination_for_associations.count())

        # Check that the related_query_names work correctly for each individual RelationshipAssociation
        self.assertEqual([self.racks[0]],
                         list(associations[0].source_dcim_rack.all()))
        self.assertEqual([self.vlans[0]],
                         list(associations[0].destination_ipam_vlan.all()))
        self.assertEqual([], list(associations[0].destination_dcim_site.all()))

        self.assertEqual([self.racks[0]],
                         list(associations[1].source_dcim_rack.all()))
        self.assertEqual([self.vlans[1]],
                         list(associations[1].destination_ipam_vlan.all()))
        self.assertEqual([], list(associations[1].destination_dcim_site.all()))

        self.assertEqual([self.racks[0]],
                         list(associations[2].source_dcim_rack.all()))
        self.assertEqual([], list(associations[2].destination_ipam_vlan.all()))
        self.assertEqual([self.sites[0]],
                         list(associations[2].destination_dcim_site.all()))

        # Check that the related query names can be used for filtering as well
        self.assertEqual(
            3,
            RelationshipAssociation.objects.filter(
                source_dcim_rack=self.racks[0]).count())
        self.assertEqual(
            2,
            RelationshipAssociation.objects.filter(
                destination_ipam_vlan__isnull=False).count())
        self.assertEqual(
            1,
            RelationshipAssociation.objects.filter(
                destination_ipam_vlan=self.vlans[0]).count())
        self.assertEqual(
            1,
            RelationshipAssociation.objects.filter(
                destination_ipam_vlan=self.vlans[1]).count())
        self.assertEqual(
            1,
            RelationshipAssociation.objects.filter(
                destination_dcim_site=self.sites[0]).count())
Beispiel #26
0
    def test_clean_check_quantity_o2m(self):
        """Validate that one-to-many relationships can't have more than one relationship association per source."""

        cra = RelationshipAssociation(relationship=self.o2m_1,
                                      source=self.sites[0],
                                      destination=self.vlans[0])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.o2m_1,
                                      source=self.sites[0],
                                      destination=self.vlans[1])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.o2m_1,
                                      source=self.sites[1],
                                      destination=self.vlans[2])
        cra.validated_save()

        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2m_1,
                                          source=self.sites[2],
                                          destination=self.vlans[0])
            cra.clean()
        expected_errors = {
            "destination": [
                "Unable to create more than one generic site to vlan association to VLAN A (100) (destination)",
            ],
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        # Shouldn't be possible to create another copy of the same RelationshipAssociation
        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.o2m_1,
                                          source=self.sites[0],
                                          destination=self.vlans[0])
            cra.validated_save()
        expected_errors = {
            "__all__": [
                "Relationship association with this Relationship, Source type, Source id, Destination type "
                "and Destination id already exists."
            ],
            "destination": [
                "Unable to create more than one generic site to vlan association to VLAN A (100) (destination)",
            ],
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)
Beispiel #27
0
    def test_clean_check_quantity_m2m(self):
        """Validate that many-to-many relationship can have many relationship associations."""
        cra = RelationshipAssociation(relationship=self.m2m_1,
                                      source=self.racks[0],
                                      destination=self.vlans[0])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.m2m_1,
                                      source=self.racks[0],
                                      destination=self.vlans[1])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.m2m_1,
                                      source=self.racks[1],
                                      destination=self.vlans[2])
        cra.validated_save()

        cra = RelationshipAssociation(relationship=self.m2m_1,
                                      source=self.racks[2],
                                      destination=self.vlans[0])
        cra.validated_save()

        # Shouldn't be possible to create another copy of the same RelationshipAssociation
        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.m2m_1,
                                          source=self.racks[0],
                                          destination=self.vlans[0])
            cra.validated_save()
        expected_errors = {
            "__all__": [
                "Relationship association with this Relationship, Source type, Source id, Destination type "
                "and Destination id already exists."
            ],
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)

        cra = RelationshipAssociation(relationship=self.m2ms_1,
                                      source=self.sites[0],
                                      destination=self.sites[1])
        cra.validated_save()

        # Shouldn't be possible to create a mirrored copy of the same symmetric RelationshipAssociation
        with self.assertRaises(ValidationError) as handler:
            cra = RelationshipAssociation(relationship=self.m2ms_1,
                                          source=self.sites[1],
                                          destination=self.sites[0])
            cra.validated_save()
        expected_errors = {
            "__all__": [
                "A Related Sites association already exists between Site B and Site A"
            ]
        }
        self.assertEqual(handler.exception.message_dict, expected_errors)