Browse Source

Added virtual chassis tests

Jeremy Stretch 8 năm trước cách đây
mục cha
commit
d6eaa3d0cc
4 tập tin đã thay đổi với 110 bổ sung258 xóa
  1. 7 17
      netbox/dcim/models.py
  2. 10 1
      netbox/dcim/signals.py
  3. 92 239
      netbox/dcim/tests/test_api.py
  4. 1 1
      netbox/templates/dcim/device.html

+ 7 - 17
netbox/dcim/models.py

@@ -1011,14 +1011,6 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
             raise ValidationError({
                 'vc_position': "A device assigned to a virtual chassis must have its position defined."
             })
-        try:
-            virtual_chassis = VirtualChassis.objects.filter(master=self.pk)
-            if self.virtual_chassis != virtual_chassis:
-                raise ValidationError(
-                    "This device has been designated the master of a virtual chassis but is not assigned to it."
-                )
-        except VirtualChassis.DoesNotExist:
-            pass
 
     def save(self, *args, **kwargs):
 
@@ -1078,7 +1070,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
     def display_name(self):
         if self.name:
             return self.name
-        elif hasattr(self, 'virtual_chassis') and self.virtual_chassis.master.name:
+        elif self.virtual_chassis and self.virtual_chassis.master.name:
             return "{}:{}".format(self.virtual_chassis.master, self.vc_position)
         elif hasattr(self, 'device_type'):
             return "{}".format(self.device_type)
@@ -1108,10 +1100,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
         """
         If this Device is a VirtualChassis member, return the VC master. Otherwise, return None.
         """
-        if hasattr(self, 'virtual_chassis'):
-            return self.virtual_chassis.master
-        else:
-            return None
+        return self.virtual_chassis.master if self.virtual_chassis else None
 
     @property
     def vc_interfaces(self):
@@ -1120,7 +1109,7 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
         Device belonging to the same VirtualChassis.
         """
         filter = Q(device=self)
-        if hasattr(self, 'virtual_chassis') and self.virtual_chassis.master == self:
+        if self.virtual_chassis and self.virtual_chassis.master == self:
             filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False)
         return Interface.objects.filter(filter)
 
@@ -1638,8 +1627,9 @@ class VirtualChassis(models.Model):
 
     def clean(self):
 
-        # Validate master assignment
-        if self.master not in self.members.all():
+        # Verify that the selected master device has been assigned to this VirtualChassis. (Skip when creating a new
+        # VirtualChassis.)
+        if self.pk and self.master not in self.members.all():
             raise ValidationError({
                 'master': "The selected master is not assigned to this virtual chassis."
-            })
+            })

+ 10 - 1
netbox/dcim/signals.py

@@ -1,11 +1,20 @@
 from __future__ import unicode_literals
 
-from django.db.models.signals import pre_delete
+from django.db.models.signals import post_save, pre_delete
 from django.dispatch import receiver
 
 from .models import Device, VirtualChassis
 
 
+@receiver(post_save, sender=VirtualChassis)
+def assign_virtualchassis_master(instance, created, **kwargs):
+    """
+    When a VirtualChassis is created, automatically assign its master device to the VC.
+    """
+    if created:
+        Device.objects.filter(pk=instance.master.pk).update(virtual_chassis=instance, vc_position=1)
+
+
 @receiver(pre_delete, sender=VirtualChassis)
 def clear_virtualchassis_members(instance, **kwargs):
     """

+ 92 - 239
netbox/dcim/tests/test_api.py

@@ -2861,9 +2861,69 @@ class VirtualChassisTest(HttpStatusMixin, APITestCase):
         token = Token.objects.create(user=user)
         self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
 
-        self.vc1 = VirtualChassis.objects.create(domain='test-domain-1')
-        self.vc2 = VirtualChassis.objects.create(domain='test-domain-2')
-        self.vc3 = VirtualChassis.objects.create(domain='test-domain-3')
+        site = Site.objects.create(name='Test Site', slug='test-site')
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer', slug='test-manufacturer')
+        device_type = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Test Device Type', slug='test-device-type'
+        )
+        device_role = DeviceRole.objects.create(
+            name='Test Device Role', slug='test-device-role', color='ff0000'
+        )
+
+        # Create 9 member Devices with 12 interfaces each
+        self.device1 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch1', site=site
+        )
+        self.device2 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch2', site=site
+        )
+        self.device3 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch3', site=site
+        )
+        self.device4 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch4', site=site
+        )
+        self.device5 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch5', site=site
+        )
+        self.device6 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch6', site=site
+        )
+        self.device7 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch7', site=site
+        )
+        self.device8 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch8', site=site
+        )
+        self.device9 = Device.objects.create(
+            device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
+        )
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device1, name='1/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device2, name='2/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device3, name='3/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device4, name='1/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device5, name='2/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device6, name='3/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device7, name='1/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device8, name='2/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+        for i in range(0, 13):
+            Interface.objects.create(device=self.device9, name='3/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
+
+        # Create two VirtualChassis with three members each
+        self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1')
+        Device.objects.filter(pk=self.device2.pk).update(virtual_chassis=self.vc1, vc_position=2)
+        Device.objects.filter(pk=self.device3.pk).update(virtual_chassis=self.vc1, vc_position=3)
+        self.vc2 = VirtualChassis.objects.create(master=self.device4, domain='test-domain-2')
+        Device.objects.filter(pk=self.device5.pk).update(virtual_chassis=self.vc2, vc_position=2)
+        Device.objects.filter(pk=self.device6.pk).update(virtual_chassis=self.vc2, vc_position=3)
 
     def test_get_virtualchassis(self):
 
@@ -2877,46 +2937,58 @@ class VirtualChassisTest(HttpStatusMixin, APITestCase):
         url = reverse('dcim-api:virtualchassis-list')
         response = self.client.get(url, **self.header)
 
-        self.assertEqual(response.data['count'], 3)
+        self.assertEqual(response.data['count'], 2)
 
     def test_create_virtualchassis(self):
 
         data = {
-            'domain': 'test-domain-4',
+            'master': self.device7.pk,
+            'domain': 'test-domain-3',
         }
 
         url = reverse('dcim-api:virtualchassis-list')
         response = self.client.post(url, data, format='json', **self.header)
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VirtualChassis.objects.count(), 4)
-        vc4 = VirtualChassis.objects.get(pk=response.data['id'])
-        self.assertEqual(vc4.domain, data['domain'])
+        self.assertEqual(VirtualChassis.objects.count(), 3)
+        vc3 = VirtualChassis.objects.get(pk=response.data['id'])
+        self.assertEqual(vc3.master.pk, data['master'])
+        self.assertEqual(vc3.domain, data['domain'])
+
+        # Verify that the master device was automatically assigned to the VC
+        self.assertTrue(Device.objects.filter(pk=vc3.master.pk, virtual_chassis=vc3.pk).exists())
 
     def test_create_virtualchassis_bulk(self):
 
         data = [
             {
-                'domain': 'test-domain-4',
+                'master': self.device7.pk,
+                'domain': 'test-domain-3',
             },
             {
-                'domain': 'test-domain-5',
+                'master': self.device8.pk,
+                'domain': 'test-domain-4',
             },
             {
-                'domain': 'test-domain-6',
+                'master': self.device9.pk,
+                'domain': 'test-domain-5',
             },
         ]
 
         url = reverse('dcim-api:virtualchassis-list')
         response = self.client.post(url, data, format='json', **self.header)
         self.assertHttpStatus(response, status.HTTP_201_CREATED)
-        self.assertEqual(VirtualChassis.objects.count(), 6)
+        self.assertEqual(VirtualChassis.objects.count(), 5)
+        self.assertEqual(response.data[0]['master'], data[0]['master'])
         self.assertEqual(response.data[0]['domain'], data[0]['domain'])
+        self.assertEqual(response.data[1]['master'], data[1]['master'])
         self.assertEqual(response.data[1]['domain'], data[1]['domain'])
+        self.assertEqual(response.data[2]['master'], data[2]['master'])
         self.assertEqual(response.data[2]['domain'], data[2]['domain'])
 
     def test_update_virtualchassis(self):
 
         data = {
+            'master': self.device2.pk,
             'domain': 'test-domain-x',
         }
 
@@ -2924,8 +2996,9 @@ class VirtualChassisTest(HttpStatusMixin, APITestCase):
         response = self.client.put(url, data, format='json', **self.header)
 
         self.assertHttpStatus(response, status.HTTP_200_OK)
-        self.assertEqual(VirtualChassis.objects.count(), 3)
+        self.assertEqual(VirtualChassis.objects.count(), 2)
         vc1 = VirtualChassis.objects.get(pk=response.data['id'])
+        self.assertEqual(vc1.master.pk, data['master'])
         self.assertEqual(vc1.domain, data['domain'])
 
     def test_delete_virtualchassis(self):
@@ -2934,230 +3007,10 @@ class VirtualChassisTest(HttpStatusMixin, APITestCase):
         response = self.client.delete(url, **self.header)
 
         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-        self.assertEqual(VirtualChassis.objects.count(), 2)
-
+        self.assertEqual(VirtualChassis.objects.count(), 1)
 
-# class VCMembershipTest(HttpStatusMixin, APITestCase):
-#
-#     def setUp(self):
-#
-#         user = User.objects.create(username='testuser', is_superuser=True)
-#         token = Token.objects.create(user=user)
-#         self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token.key)}
-#
-#         site = Site.objects.create(name='Test Site', slug='test-site')
-#         manufacturer = Manufacturer.objects.create(name='Test Manufacturer', slug='test-manufacturer')
-#         device_type = DeviceType.objects.create(
-#             manufacturer=manufacturer, model='Test Device Type', slug='test-device-type'
-#         )
-#         device_role = DeviceRole.objects.create(
-#             name='Test Device Role', slug='test-device-role', color='ff0000'
-#         )
-#
-#         # Create 9 member Devices with 12 interfaces each
-#         self.device1 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch1', site=site
-#         )
-#         self.device2 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch2', site=site
-#         )
-#         self.device3 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch3', site=site
-#         )
-#         self.device4 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch4', site=site
-#         )
-#         self.device5 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch5', site=site
-#         )
-#         self.device6 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch6', site=site
-#         )
-#         self.device7 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch7', site=site
-#         )
-#         self.device8 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch8', site=site
-#         )
-#         self.device9 = Device.objects.create(
-#             device_type=device_type, device_role=device_role, name='StackSwitch9', site=site
-#         )
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device1, name='1/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device2, name='2/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device3, name='3/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device4, name='1/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device5, name='2/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device6, name='3/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device7, name='1/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device8, name='2/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#         for i in range(0, 13):
-#             Interface.objects.create(device=self.device9, name='3/{}'.format(i), form_factor=IFACE_FF_1GE_FIXED)
-#
-#         # Create two VirtualChassis with three members each
-#         self.vc1 = VirtualChassis.objects.create(domain='test-domain-1')
-#         self.vc2 = VirtualChassis.objects.create(domain='test-domain-2')
-#         self.vcm1 = VCMembership.objects.create(
-#             virtual_chassis=self.vc1, device=self.device1, position=1, priority=10, is_master=True
-#         )
-#         self.vcm2 = VCMembership.objects.create(
-#             virtual_chassis=self.vc1, device=self.device2, position=2, priority=20
-#         )
-#         self.vcm3 = VCMembership.objects.create(
-#             virtual_chassis=self.vc1, device=self.device3, position=3, priority=30
-#         )
-#         self.vcm4 = VCMembership.objects.create(
-#             virtual_chassis=self.vc2, device=self.device4, position=1, priority=10, is_master=True
-#         )
-#         self.vcm5 = VCMembership.objects.create(
-#             virtual_chassis=self.vc2, device=self.device5, position=2, priority=20
-#         )
-#         self.vcm6 = VCMembership.objects.create(
-#             virtual_chassis=self.vc2, device=self.device6, position=3, priority=30
-#         )
-#
-#     def test_get_vcmembership(self):
-#
-#         url = reverse('dcim-api:vcmembership-detail', kwargs={'pk': self.vcm1.pk})
-#         response = self.client.get(url, **self.header)
-#
-#         self.assertEqual(response.data['virtual_chassis']['id'], self.vc1.pk)
-#         self.assertEqual(response.data['device']['id'], self.device1.pk)
-#         self.assertEqual(response.data['position'], 1)
-#         self.assertEqual(response.data['is_master'], True)
-#         self.assertEqual(response.data['priority'], 10)
-#
-#     def test_list_vcmemberships(self):
-#
-#         url = reverse('dcim-api:vcmembership-list')
-#         response = self.client.get(url, **self.header)
-#
-#         self.assertEqual(response.data['count'], 6)
-#
-#     def test_create_vcmembership(self):
-#
-#         url = reverse('dcim-api:vcmembership-list')
-#
-#         # Try creating the first membership without is_master. This should fail.
-#         data = {
-#             'device': self.device7.pk,
-#             'position': 1,
-#             'priority': 10,
-#         }
-#         response = self.client.post(url, data, format='json', **self.header)
-#         self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
-#
-#         # Add is_master=True and try again. This should succeed.
-#         data.update({
-#             'is_master': True,
-#         })
-#         response = self.client.post(url, data, format='json', **self.header)
-#         self.assertHttpStatus(response, status.HTTP_201_CREATED)
-#         virtualchassis_id = VirtualChassis.objects.get(pk=response.data['virtual_chassis']).pk
-#
-#         # Try adding a second member with the same position
-#         data = {
-#             'virtual_chassis': virtualchassis_id,
-#             'device': self.device8.pk,
-#             'position': 1,
-#             'priority': 20,
-#         }
-#         response = self.client.post(url, data, format='json', **self.header)
-#         self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
-#
-#         # Try adding a second member with is_master=True
-#         data['is_master'] = True
-#         response = self.client.post(url, data, format='json', **self.header)
-#         self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
-#
-#         # Add a second member (valid)
-#         del(data['is_master'])
-#         data['position'] = 2
-#         response = self.client.post(url, data, format='json', **self.header)
-#         self.assertHttpStatus(response, status.HTTP_201_CREATED)
-#
-#         # Add a third member (valid)
-#         data = {
-#             'virtual_chassis': virtualchassis_id,
-#             'device': self.device9.pk,
-#             'position': 3,
-#             'priority': 30,
-#         }
-#         response = self.client.post(url, data, format='json', **self.header)
-#         self.assertHttpStatus(response, status.HTTP_201_CREATED)
-#
-#         self.assertEqual(VCMembership.objects.count(), 9)
-#
-#     def test_create_vcmembership_bulk(self):
-#
-#         vc3 = VirtualChassis.objects.create()
-#
-#         data = [
-#             # Set the master of an existing VC
-#             {
-#                 'virtual_chassis': vc3.pk,
-#                 'device': self.device7.pk,
-#                 'position': 1,
-#                 'is_master': True,
-#                 'priority': 10,
-#             },
-#             # Add a non-master member to a VC
-#             {
-#                 'virtual_chassis': vc3.pk,
-#                 'device': self.device8.pk,
-#                 'position': 2,
-#                 'is_master': False,
-#                 'priority': 20,
-#             },
-#             # Force the creation of a new VC
-#             {
-#                 'device': self.device9.pk,
-#                 'position': 1,
-#                 'is_master': True,
-#                 'priority': 10,
-#             },
-#         ]
-#
-#         url = reverse('dcim-api:vcmembership-list')
-#         response = self.client.post(url, data, format='json', **self.header)
-#
-#         self.assertHttpStatus(response, status.HTTP_201_CREATED)
-#         self.assertEqual(VirtualChassis.objects.count(), 4)
-#         self.assertEqual(VCMembership.objects.count(), 9)
-#         self.assertEqual(response.data[0]['device'], data[0]['device'])
-#         self.assertEqual(response.data[1]['device'], data[1]['device'])
-#         self.assertEqual(response.data[2]['device'], data[2]['device'])
-#
-#     def test_update_vcmembership(self):
-#
-#         data = {
-#             'virtual_chassis': self.vc2.pk,
-#             'device': self.device7.pk,
-#             'position': 9,
-#             'priority': 90,
-#         }
-#
-#         url = reverse('dcim-api:vcmembership-detail', kwargs={'pk': self.vcm3.pk})
-#         response = self.client.put(url, data, format='json', **self.header)
-#
-#         self.assertHttpStatus(response, status.HTTP_200_OK)
-#         vcm3 = VCMembership.objects.get(pk=response.data['id'])
-#         self.assertEqual(vcm3.virtual_chassis.pk, data['virtual_chassis'])
-#         self.assertEqual(vcm3.device.pk, data['device'])
-#         self.assertEqual(vcm3.position, data['position'])
-#         self.assertEqual(vcm3.priority, data['priority'])
-#
-#     def test_delete_vcmembership(self):
-#
-#         url = reverse('dcim-api:vcmembership-detail', kwargs={'pk': self.vcm3.pk})
-#         response = self.client.delete(url, **self.header)
-#
-#         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
-#         self.assertEqual(VCMembership.objects.count(), 5)
+        # Verify that all VC members have had their VC-related fields nullified
+        for d in [self.device1, self.device2, self.device3]:
+            self.assertTrue(
+                Device.objects.filter(pk=d.pk, virtual_chassis=None, vc_position=None, vc_priority=None)
+            )

+ 1 - 1
netbox/templates/dcim/device.html

@@ -111,7 +111,7 @@
                         <th>Priority</th>
                     </tr>
                     {% for vc_member in vc_members %}
-                        <tr{% if vc_member.device == device %} class="success"{% endif %}>
+                        <tr{% if vc_member == device %} class="success"{% endif %}>
                             <td>
                                 <a href="{{ vc_member.get_absolute_url }}">{{ vc_member }}</a>
                             </td>