test_models.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384
  1. from django.core.exceptions import ValidationError
  2. from django.test import TestCase, tag
  3. from circuits.models import *
  4. from core.models import ObjectType
  5. from dcim.choices import *
  6. from dcim.models import *
  7. from extras.models import CustomField
  8. from ipam.models import Prefix
  9. from netbox.choices import WeightUnitChoices
  10. from tenancy.models import Tenant
  11. from utilities.data import drange
  12. from virtualization.models import Cluster, ClusterType
  13. class MACAddressTestCase(TestCase):
  14. @classmethod
  15. def setUpTestData(cls):
  16. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  17. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  18. device_type = DeviceType.objects.create(
  19. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  20. )
  21. device_role = DeviceRole.objects.create(name='Test Role 1', slug='test-role-1')
  22. device = Device.objects.create(
  23. name='Device 1', device_type=device_type, role=device_role, site=site,
  24. )
  25. cls.interface = Interface.objects.create(
  26. device=device,
  27. name='Interface 1',
  28. type=InterfaceTypeChoices.TYPE_1GE_FIXED,
  29. mgmt_only=True
  30. )
  31. cls.mac_a = MACAddress.objects.create(mac_address='1234567890ab', assigned_object=cls.interface)
  32. cls.mac_b = MACAddress.objects.create(mac_address='1234567890ba', assigned_object=cls.interface)
  33. cls.interface.primary_mac_address = cls.mac_a
  34. cls.interface.save()
  35. @tag('regression')
  36. def test_clean_will_not_allow_removal_of_assigned_object_if_primary(self):
  37. self.mac_a.assigned_object = None
  38. with self.assertRaisesMessage(ValidationError, 'Cannot unassign MAC Address while'):
  39. self.mac_a.clean()
  40. @tag('regression')
  41. def test_clean_will_allow_removal_of_assigned_object_if_not_primary(self):
  42. self.mac_b.assigned_object = None
  43. self.mac_b.clean()
  44. class LocationTestCase(TestCase):
  45. def test_change_location_site(self):
  46. """
  47. Check that all child Locations and Racks get updated when a Location is moved to a new Site. Topology:
  48. Site A
  49. - Location A1
  50. - Location A2
  51. - Rack 2
  52. - Device 2
  53. - Rack 1
  54. - Device 1
  55. """
  56. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  57. device_type = DeviceType.objects.create(
  58. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  59. )
  60. role = DeviceRole.objects.create(
  61. name='Device Role 1', slug='device-role-1', color='ff0000'
  62. )
  63. site_a = Site.objects.create(name='Site A', slug='site-a')
  64. site_b = Site.objects.create(name='Site B', slug='site-b')
  65. location_a1 = Location(site=site_a, name='Location A1', slug='location-a1')
  66. location_a1.save()
  67. location_a2 = Location(site=site_a, parent=location_a1, name='Location A2', slug='location-a2')
  68. location_a2.save()
  69. rack1 = Rack.objects.create(site=site_a, location=location_a1, name='Rack 1')
  70. rack2 = Rack.objects.create(site=site_a, location=location_a2, name='Rack 2')
  71. device1 = Device.objects.create(
  72. site=site_a,
  73. location=location_a1,
  74. name='Device 1',
  75. device_type=device_type,
  76. role=role
  77. )
  78. device2 = Device.objects.create(
  79. site=site_a,
  80. location=location_a2,
  81. name='Device 2',
  82. device_type=device_type,
  83. role=role
  84. )
  85. powerpanel1 = PowerPanel.objects.create(site=site_a, location=location_a1, name='Power Panel 1')
  86. # Move Location A1 to Site B
  87. location_a1.site = site_b
  88. location_a1.save()
  89. # Check that all objects within Location A1 now belong to Site B
  90. self.assertEqual(Location.objects.get(pk=location_a1.pk).site, site_b)
  91. self.assertEqual(Location.objects.get(pk=location_a2.pk).site, site_b)
  92. self.assertEqual(Rack.objects.get(pk=rack1.pk).site, site_b)
  93. self.assertEqual(Rack.objects.get(pk=rack2.pk).site, site_b)
  94. self.assertEqual(Device.objects.get(pk=device1.pk).site, site_b)
  95. self.assertEqual(Device.objects.get(pk=device2.pk).site, site_b)
  96. self.assertEqual(PowerPanel.objects.get(pk=powerpanel1.pk).site, site_b)
  97. class RackTypeTestCase(TestCase):
  98. @classmethod
  99. def setUpTestData(cls):
  100. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  101. RackType.objects.create(
  102. manufacturer=manufacturer,
  103. model='RackType 1',
  104. slug='rack-type-1',
  105. width=11,
  106. u_height=22,
  107. starting_unit=3,
  108. desc_units=True,
  109. outer_width=444,
  110. outer_depth=5,
  111. outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER,
  112. weight=66,
  113. weight_unit=WeightUnitChoices.UNIT_POUND,
  114. max_weight=7777,
  115. mounting_depth=8,
  116. )
  117. def test_rack_creation(self):
  118. rack_type = RackType.objects.first()
  119. sites = (
  120. Site(name='Site 1', slug='site-1'),
  121. )
  122. Site.objects.bulk_create(sites)
  123. locations = (
  124. Location(name='Location 1', slug='location-1', site=sites[0]),
  125. )
  126. for location in locations:
  127. location.save()
  128. rack = Rack.objects.create(
  129. name='Rack 1',
  130. facility_id='A101',
  131. site=sites[0],
  132. location=locations[0],
  133. rack_type=rack_type
  134. )
  135. self.assertEqual(rack.width, rack_type.width)
  136. self.assertEqual(rack.u_height, rack_type.u_height)
  137. self.assertEqual(rack.starting_unit, rack_type.starting_unit)
  138. self.assertEqual(rack.desc_units, rack_type.desc_units)
  139. self.assertEqual(rack.outer_width, rack_type.outer_width)
  140. self.assertEqual(rack.outer_depth, rack_type.outer_depth)
  141. self.assertEqual(rack.outer_unit, rack_type.outer_unit)
  142. self.assertEqual(rack.weight, rack_type.weight)
  143. self.assertEqual(rack.weight_unit, rack_type.weight_unit)
  144. self.assertEqual(rack.max_weight, rack_type.max_weight)
  145. self.assertEqual(rack.mounting_depth, rack_type.mounting_depth)
  146. class RackTestCase(TestCase):
  147. @classmethod
  148. def setUpTestData(cls):
  149. sites = (
  150. Site(name='Site 1', slug='site-1'),
  151. Site(name='Site 2', slug='site-2'),
  152. )
  153. Site.objects.bulk_create(sites)
  154. locations = (
  155. Location(name='Location 1', slug='location-1', site=sites[0]),
  156. Location(name='Location 2', slug='location-2', site=sites[1]),
  157. )
  158. for location in locations:
  159. location.save()
  160. Rack.objects.create(
  161. name='Rack 1',
  162. facility_id='A101',
  163. site=sites[0],
  164. location=locations[0],
  165. u_height=42
  166. )
  167. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  168. device_types = (
  169. DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', u_height=1),
  170. DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=0),
  171. DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3', u_height=0.5),
  172. )
  173. DeviceType.objects.bulk_create(device_types)
  174. DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
  175. def test_rack_device_outside_height(self):
  176. site = Site.objects.first()
  177. rack = Rack.objects.first()
  178. device1 = Device(
  179. name='Device 1',
  180. device_type=DeviceType.objects.first(),
  181. role=DeviceRole.objects.first(),
  182. site=site,
  183. rack=rack,
  184. position=43,
  185. face=DeviceFaceChoices.FACE_FRONT,
  186. )
  187. device1.save()
  188. with self.assertRaises(ValidationError):
  189. rack.clean()
  190. def test_location_site(self):
  191. site1 = Site.objects.get(name='Site 1')
  192. location2 = Location.objects.get(name='Location 2')
  193. rack2 = Rack(
  194. name='Rack 2',
  195. site=site1,
  196. location=location2,
  197. u_height=42
  198. )
  199. rack2.save()
  200. with self.assertRaises(ValidationError):
  201. rack2.clean()
  202. def test_mount_single_device(self):
  203. site = Site.objects.first()
  204. rack = Rack.objects.first()
  205. device1 = Device(
  206. name='TestSwitch1',
  207. device_type=DeviceType.objects.first(),
  208. role=DeviceRole.objects.first(),
  209. site=site,
  210. rack=rack,
  211. position=10.0,
  212. face=DeviceFaceChoices.FACE_REAR,
  213. )
  214. device1.save()
  215. # Validate rack height
  216. self.assertEqual(list(rack.units), list(drange(42.5, 0.5, -0.5)))
  217. # Validate inventory (front face)
  218. rack1_inventory_front = {
  219. u['id']: u for u in rack.get_rack_units(face=DeviceFaceChoices.FACE_FRONT)
  220. }
  221. self.assertEqual(rack1_inventory_front[10.0]['device'], device1)
  222. self.assertEqual(rack1_inventory_front[10.5]['device'], device1)
  223. del rack1_inventory_front[10.0]
  224. del rack1_inventory_front[10.5]
  225. for u in rack1_inventory_front.values():
  226. self.assertIsNone(u['device'])
  227. # Validate inventory (rear face)
  228. rack1_inventory_rear = {
  229. u['id']: u for u in rack.get_rack_units(face=DeviceFaceChoices.FACE_REAR)
  230. }
  231. self.assertEqual(rack1_inventory_rear[10.0]['device'], device1)
  232. self.assertEqual(rack1_inventory_rear[10.5]['device'], device1)
  233. del rack1_inventory_rear[10.0]
  234. del rack1_inventory_rear[10.5]
  235. for u in rack1_inventory_rear.values():
  236. self.assertIsNone(u['device'])
  237. def test_mount_zero_ru(self):
  238. """
  239. Check that a 0RU device can be mounted in a rack with no face/position.
  240. """
  241. site = Site.objects.first()
  242. rack = Rack.objects.first()
  243. Device(
  244. name='Device 1',
  245. role=DeviceRole.objects.first(),
  246. device_type=DeviceType.objects.first(),
  247. site=site,
  248. rack=rack
  249. ).save()
  250. def test_mount_half_u_devices(self):
  251. """
  252. Check that two 0.5U devices can be mounted in the same rack unit.
  253. """
  254. rack = Rack.objects.first()
  255. attrs = {
  256. 'device_type': DeviceType.objects.get(u_height=0.5),
  257. 'role': DeviceRole.objects.first(),
  258. 'site': Site.objects.first(),
  259. 'rack': rack,
  260. 'face': DeviceFaceChoices.FACE_FRONT,
  261. }
  262. Device(name='Device 1', position=1, **attrs).save()
  263. Device(name='Device 2', position=1.5, **attrs).save()
  264. self.assertEqual(len(rack.get_available_units()), rack.u_height * 2 - 3)
  265. def test_change_rack_site(self):
  266. """
  267. Check that child Devices get updated when a Rack is moved to a new Site.
  268. """
  269. site_a = Site.objects.create(name='Site A', slug='site-a')
  270. site_b = Site.objects.create(name='Site B', slug='site-b')
  271. # Create Rack1 in Site A
  272. rack1 = Rack.objects.create(site=site_a, name='Rack 1')
  273. # Create Device1 in Rack1
  274. device1 = Device.objects.create(
  275. site=site_a,
  276. rack=rack1,
  277. device_type=DeviceType.objects.first(),
  278. role=DeviceRole.objects.first()
  279. )
  280. # Move Rack1 to Site B
  281. rack1.site = site_b
  282. rack1.save()
  283. # Check that Device1 is now assigned to Site B
  284. self.assertEqual(Device.objects.get(pk=device1.pk).site, site_b)
  285. def test_utilization(self):
  286. site = Site.objects.first()
  287. rack = Rack.objects.first()
  288. Device(
  289. name='Device 1',
  290. role=DeviceRole.objects.first(),
  291. device_type=DeviceType.objects.first(),
  292. site=site,
  293. rack=rack,
  294. position=1
  295. ).save()
  296. rack.refresh_from_db()
  297. self.assertEqual(rack.get_utilization(), 1 / 42 * 100)
  298. # create device excluded from utilization calculations
  299. dt = DeviceType.objects.create(
  300. manufacturer=Manufacturer.objects.first(),
  301. model='Device Type 4',
  302. slug='device-type-4',
  303. u_height=1,
  304. exclude_from_utilization=True
  305. )
  306. Device(
  307. name='Device 2',
  308. role=DeviceRole.objects.first(),
  309. device_type=dt,
  310. site=site,
  311. rack=rack,
  312. position=5
  313. ).save()
  314. rack.refresh_from_db()
  315. self.assertEqual(rack.get_utilization(), 1 / 42 * 100)
  316. class DeviceTestCase(TestCase):
  317. @classmethod
  318. def setUpTestData(cls):
  319. Site.objects.create(name='Test Site 1', slug='test-site-1')
  320. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  321. device_type = DeviceType.objects.create(
  322. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  323. )
  324. roles = (
  325. DeviceRole(name='Test Role 1', slug='test-role-1'),
  326. DeviceRole(name='Test Role 2', slug='test-role-2'),
  327. )
  328. for role in roles:
  329. role.save()
  330. # Create a CustomField with a default value & assign it to all component models
  331. cf1 = CustomField.objects.create(name='cf1', default='foo')
  332. cf1.object_types.set(
  333. ObjectType.objects.filter(app_label='dcim', model__in=[
  334. 'consoleport',
  335. 'consoleserverport',
  336. 'powerport',
  337. 'poweroutlet',
  338. 'interface',
  339. 'rearport',
  340. 'frontport',
  341. 'modulebay',
  342. 'devicebay',
  343. 'inventoryitem',
  344. ])
  345. )
  346. # Create DeviceType components
  347. ConsolePortTemplate(
  348. device_type=device_type,
  349. name='Console Port 1'
  350. ).save()
  351. ConsoleServerPortTemplate(
  352. device_type=device_type,
  353. name='Console Server Port 1'
  354. ).save()
  355. powerport = PowerPortTemplate(
  356. device_type=device_type,
  357. name='Power Port 1',
  358. maximum_draw=1000,
  359. allocated_draw=500
  360. )
  361. powerport.save()
  362. PowerOutletTemplate(
  363. device_type=device_type,
  364. name='Power Outlet 1',
  365. power_port=powerport,
  366. feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
  367. ).save()
  368. InterfaceTemplate(
  369. device_type=device_type,
  370. name='Interface 1',
  371. type=InterfaceTypeChoices.TYPE_1GE_FIXED,
  372. mgmt_only=True
  373. ).save()
  374. rearport = RearPortTemplate(
  375. device_type=device_type,
  376. name='Rear Port 1',
  377. type=PortTypeChoices.TYPE_8P8C,
  378. positions=8
  379. )
  380. rearport.save()
  381. frontport = FrontPortTemplate(
  382. device_type=device_type,
  383. name='Front Port 1',
  384. type=PortTypeChoices.TYPE_8P8C,
  385. )
  386. frontport.save()
  387. PortTemplateMapping.objects.create(
  388. device_type=device_type,
  389. front_port=frontport,
  390. rear_port=rearport,
  391. rear_port_position=2,
  392. )
  393. ModuleBayTemplate(
  394. device_type=device_type,
  395. name='Module Bay 1'
  396. ).save()
  397. DeviceBayTemplate(
  398. device_type=device_type,
  399. name='Device Bay 1'
  400. ).save()
  401. InventoryItemTemplate(
  402. device_type=device_type,
  403. name='Inventory Item 1'
  404. ).save()
  405. def test_device_creation(self):
  406. """
  407. Ensure that all Device components are copied automatically from the DeviceType.
  408. """
  409. device = Device(
  410. site=Site.objects.first(),
  411. device_type=DeviceType.objects.first(),
  412. role=DeviceRole.objects.first(),
  413. name='Test Device 1'
  414. )
  415. device.save()
  416. consoleport = ConsolePort.objects.get(
  417. device=device,
  418. name='Console Port 1'
  419. )
  420. self.assertEqual(consoleport.cf['cf1'], 'foo')
  421. consoleserverport = ConsoleServerPort.objects.get(
  422. device=device,
  423. name='Console Server Port 1'
  424. )
  425. self.assertEqual(consoleserverport.cf['cf1'], 'foo')
  426. powerport = PowerPort.objects.get(
  427. device=device,
  428. name='Power Port 1',
  429. maximum_draw=1000,
  430. allocated_draw=500
  431. )
  432. self.assertEqual(powerport.cf['cf1'], 'foo')
  433. poweroutlet = PowerOutlet.objects.get(
  434. device=device,
  435. name='Power Outlet 1',
  436. power_port=powerport,
  437. feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A,
  438. status=PowerOutletStatusChoices.STATUS_ENABLED,
  439. )
  440. self.assertEqual(poweroutlet.cf['cf1'], 'foo')
  441. interface = Interface.objects.get(
  442. device=device,
  443. name='Interface 1',
  444. type=InterfaceTypeChoices.TYPE_1GE_FIXED,
  445. mgmt_only=True
  446. )
  447. self.assertEqual(interface.cf['cf1'], 'foo')
  448. rearport = RearPort.objects.get(
  449. device=device,
  450. name='Rear Port 1',
  451. type=PortTypeChoices.TYPE_8P8C,
  452. positions=8
  453. )
  454. self.assertEqual(rearport.cf['cf1'], 'foo')
  455. frontport = FrontPort.objects.get(
  456. device=device,
  457. name='Front Port 1',
  458. type=PortTypeChoices.TYPE_8P8C,
  459. positions=1
  460. )
  461. self.assertEqual(frontport.cf['cf1'], 'foo')
  462. self.assertTrue(PortMapping.objects.filter(front_port=frontport, rear_port=rearport).exists())
  463. modulebay = ModuleBay.objects.get(
  464. device=device,
  465. name='Module Bay 1'
  466. )
  467. self.assertEqual(modulebay.cf['cf1'], 'foo')
  468. devicebay = DeviceBay.objects.get(
  469. device=device,
  470. name='Device Bay 1'
  471. )
  472. self.assertEqual(devicebay.cf['cf1'], 'foo')
  473. inventoryitem = InventoryItem.objects.get(
  474. device=device,
  475. name='Inventory Item 1'
  476. )
  477. self.assertEqual(inventoryitem.cf['cf1'], 'foo')
  478. def test_multiple_unnamed_devices(self):
  479. device1 = Device(
  480. site=Site.objects.first(),
  481. device_type=DeviceType.objects.first(),
  482. role=DeviceRole.objects.first(),
  483. name=None
  484. )
  485. device1.save()
  486. device2 = Device(
  487. site=device1.site,
  488. device_type=device1.device_type,
  489. role=device1.role,
  490. name=None
  491. )
  492. device2.full_clean()
  493. device2.save()
  494. self.assertEqual(Device.objects.filter(name__isnull=True).count(), 2)
  495. def test_device_name_case_sensitivity(self):
  496. device1 = Device(
  497. site=Site.objects.first(),
  498. device_type=DeviceType.objects.first(),
  499. role=DeviceRole.objects.first(),
  500. name='device 1'
  501. )
  502. device1.save()
  503. device2 = Device(
  504. site=device1.site,
  505. device_type=device1.device_type,
  506. role=device1.role,
  507. name='DEVICE 1'
  508. )
  509. # Uniqueness validation for name should ignore case
  510. with self.assertRaises(ValidationError):
  511. device2.full_clean()
  512. def test_device_duplicate_names(self):
  513. device1 = Device(
  514. site=Site.objects.first(),
  515. device_type=DeviceType.objects.first(),
  516. role=DeviceRole.objects.first(),
  517. name='Test Device 1'
  518. )
  519. device1.save()
  520. device2 = Device(
  521. site=device1.site,
  522. device_type=device1.device_type,
  523. role=device1.role,
  524. name=device1.name
  525. )
  526. # Two devices assigned to the same Site and no Tenant should fail validation
  527. with self.assertRaises(ValidationError):
  528. device2.full_clean()
  529. tenant = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
  530. device1.tenant = tenant
  531. device1.save()
  532. device2.tenant = tenant
  533. # Two devices assigned to the same Site and the same Tenant should fail validation
  534. with self.assertRaises(ValidationError):
  535. device2.full_clean()
  536. device2.tenant = None
  537. # Two devices assigned to the same Site and different Tenants should pass validation
  538. device2.full_clean()
  539. device2.save()
  540. def test_device_label(self):
  541. device1 = Device(
  542. site=Site.objects.first(),
  543. device_type=DeviceType.objects.first(),
  544. role=DeviceRole.objects.first(),
  545. name=None,
  546. )
  547. self.assertEqual(device1.label, None)
  548. device1.name = 'Test Device 1'
  549. self.assertEqual(device1.label, 'Test Device 1')
  550. virtual_chassis = VirtualChassis.objects.create(name='VC 1')
  551. device2 = Device(
  552. site=Site.objects.first(),
  553. device_type=DeviceType.objects.first(),
  554. role=DeviceRole.objects.first(),
  555. name=None,
  556. virtual_chassis=virtual_chassis,
  557. vc_position=2,
  558. )
  559. self.assertEqual(device2.label, 'VC 1:2')
  560. device2.name = 'Test Device 2'
  561. self.assertEqual(device2.label, 'Test Device 2')
  562. def test_device_mismatched_site_cluster(self):
  563. cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
  564. Cluster.objects.create(name='Cluster 1', type=cluster_type)
  565. sites = (
  566. Site(name='Site 1', slug='site-1'),
  567. Site(name='Site 2', slug='site-2'),
  568. )
  569. Site.objects.bulk_create(sites)
  570. clusters = (
  571. Cluster(name='Cluster 1', type=cluster_type, scope=sites[0]),
  572. Cluster(name='Cluster 2', type=cluster_type, scope=sites[1]),
  573. Cluster(name='Cluster 3', type=cluster_type, scope=None),
  574. )
  575. for cluster in clusters:
  576. cluster.save()
  577. device_type = DeviceType.objects.first()
  578. device_role = DeviceRole.objects.first()
  579. # Device with site only should pass
  580. Device(
  581. name='device1',
  582. site=sites[0],
  583. device_type=device_type,
  584. role=device_role
  585. ).full_clean()
  586. # Device with site, cluster non-site should pass
  587. Device(
  588. name='device1',
  589. site=sites[0],
  590. device_type=device_type,
  591. role=device_role,
  592. cluster=clusters[2]
  593. ).full_clean()
  594. # Device with mismatched site & cluster should fail
  595. with self.assertRaises(ValidationError):
  596. Device(
  597. name='device1',
  598. site=sites[0],
  599. device_type=device_type,
  600. role=device_role,
  601. cluster=clusters[1]
  602. ).full_clean()
  603. class ModuleBayTestCase(TestCase):
  604. @classmethod
  605. def setUpTestData(cls):
  606. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  607. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  608. device_type = DeviceType.objects.create(
  609. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  610. )
  611. device_role = DeviceRole.objects.create(name='Test Role 1', slug='test-role-1')
  612. # Create a CustomField with a default value & assign it to all component models
  613. location = Location.objects.create(name='Location 1', slug='location-1', site=site)
  614. rack = Rack.objects.create(name='Rack 1', site=site)
  615. device = Device.objects.create(
  616. name='Device 1', device_type=device_type, role=device_role, site=site, location=location, rack=rack
  617. )
  618. module_bays = (
  619. ModuleBay(device=device, name='Module Bay 1', label='A', description='First'),
  620. ModuleBay(device=device, name='Module Bay 2', label='B', description='Second'),
  621. ModuleBay(device=device, name='Module Bay 3', label='C', description='Third'),
  622. )
  623. for module_bay in module_bays:
  624. module_bay.save()
  625. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  626. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
  627. modules = (
  628. Module(device=device, module_bay=module_bays[0], module_type=module_type),
  629. Module(device=device, module_bay=module_bays[1], module_type=module_type),
  630. Module(device=device, module_bay=module_bays[2], module_type=module_type),
  631. )
  632. # M3 -> MB3 -> M2 -> MB2 -> M1 -> MB1
  633. Module.objects.bulk_create(modules)
  634. module_bays[1].module = modules[0]
  635. module_bays[1].clean()
  636. module_bays[1].save()
  637. module_bays[2].module = modules[1]
  638. module_bays[2].clean()
  639. module_bays[2].save()
  640. def test_module_bay_recursion(self):
  641. module_bay_1 = ModuleBay.objects.get(name='Module Bay 1')
  642. module_bay_3 = ModuleBay.objects.get(name='Module Bay 3')
  643. module_1 = Module.objects.get(module_bay=module_bay_1)
  644. module_3 = Module.objects.get(module_bay=module_bay_3)
  645. # Confirm error if ModuleBay recurses
  646. with self.assertRaises(ValidationError):
  647. module_bay_1.module = module_3
  648. module_bay_1.clean()
  649. module_bay_1.save()
  650. # Confirm error if Module recurses
  651. with self.assertRaises(ValidationError):
  652. module_1.module_bay = module_bay_3
  653. module_1.clean()
  654. module_1.save()
  655. def test_single_module_token(self):
  656. device_type = DeviceType.objects.first()
  657. device_role = DeviceRole.objects.first()
  658. site = Site.objects.first()
  659. location = Location.objects.first()
  660. rack = Rack.objects.first()
  661. # Create DeviceType components
  662. ConsolePortTemplate.objects.create(
  663. device_type=device_type,
  664. name='{module}',
  665. label='{module}',
  666. )
  667. ModuleBayTemplate.objects.create(
  668. device_type=device_type,
  669. name='Module Bay 1'
  670. )
  671. device = Device.objects.create(
  672. name='Device 2',
  673. device_type=device_type,
  674. role=device_role,
  675. site=site,
  676. location=location,
  677. rack=rack
  678. )
  679. device.consoleports.first()
  680. @tag('regression') # #19918
  681. def test_nested_module_bay_label_resolution(self):
  682. """Test that nested module bay labels properly resolve {module} placeholders"""
  683. manufacturer = Manufacturer.objects.first()
  684. site = Site.objects.first()
  685. device_role = DeviceRole.objects.first()
  686. # Create device type with module bay template (position='A')
  687. device_type = DeviceType.objects.create(
  688. manufacturer=manufacturer,
  689. model='Device with Bays',
  690. slug='device-with-bays'
  691. )
  692. ModuleBayTemplate.objects.create(
  693. device_type=device_type,
  694. name='Bay A',
  695. position='A'
  696. )
  697. # Create module type with nested bay template using {module} placeholder
  698. module_type = ModuleType.objects.create(
  699. manufacturer=manufacturer,
  700. model='Module with Nested Bays'
  701. )
  702. ModuleBayTemplate.objects.create(
  703. module_type=module_type,
  704. name='SFP {module}-21',
  705. label='{module}-21',
  706. position='21'
  707. )
  708. # Create device and install module
  709. device = Device.objects.create(
  710. name='Test Device',
  711. device_type=device_type,
  712. role=device_role,
  713. site=site
  714. )
  715. module_bay = device.modulebays.get(name='Bay A')
  716. module = Module.objects.create(
  717. device=device,
  718. module_bay=module_bay,
  719. module_type=module_type
  720. )
  721. # Verify nested bay label resolves {module} to parent position
  722. nested_bay = module.modulebays.get(name='SFP A-21')
  723. self.assertEqual(nested_bay.label, 'A-21')
  724. @tag('regression') # #20912
  725. def test_module_bay_parent_cleared_when_module_removed(self):
  726. """Test that the parent field is properly cleared when a module bay's module assignment is removed"""
  727. device = Device.objects.first()
  728. manufacturer = Manufacturer.objects.first()
  729. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test Module Type')
  730. bay1 = ModuleBay.objects.create(device=device, name='Test Bay 1')
  731. bay2 = ModuleBay.objects.create(device=device, name='Test Bay 2')
  732. # Install a module in bay1
  733. module1 = Module.objects.create(device=device, module_bay=bay1, module_type=module_type)
  734. # Assign bay2 to module1 and verify parent is now set to bay1 (module1's bay)
  735. bay2.module = module1
  736. bay2.save()
  737. bay2.refresh_from_db()
  738. self.assertEqual(bay2.parent, bay1)
  739. self.assertEqual(bay2.module, module1)
  740. # Clear the module assignment (return bay2 to device level) Verify parent is cleared
  741. bay2.module = None
  742. bay2.save()
  743. bay2.refresh_from_db()
  744. self.assertIsNone(bay2.parent)
  745. self.assertIsNone(bay2.module)
  746. def test_module_installation_creates_port_mappings(self):
  747. """
  748. Test that installing a module with front/rear port templates correctly
  749. creates PortMapping instances for the device.
  750. """
  751. device = Device.objects.first()
  752. manufacturer = Manufacturer.objects.first()
  753. module_bay = ModuleBay.objects.create(device=device, name='Test Bay PortMapping 1')
  754. # Create a module type with a rear port template
  755. module_type_with_mappings = ModuleType.objects.create(
  756. manufacturer=manufacturer,
  757. model='Module Type With Mappings',
  758. )
  759. # Create a rear port template with 12 positions (splice)
  760. rear_port_template = RearPortTemplate.objects.create(
  761. module_type=module_type_with_mappings,
  762. name='Rear Port 1',
  763. type=PortTypeChoices.TYPE_SPLICE,
  764. positions=12,
  765. )
  766. # Create 12 front port templates mapped to the rear port
  767. front_port_templates = []
  768. for i in range(1, 13):
  769. front_port_template = FrontPortTemplate.objects.create(
  770. module_type=module_type_with_mappings,
  771. name=f'port {i}',
  772. type=PortTypeChoices.TYPE_LC,
  773. positions=1,
  774. )
  775. front_port_templates.append(front_port_template)
  776. # Create port template mapping
  777. PortTemplateMapping.objects.create(
  778. device_type=None,
  779. module_type=module_type_with_mappings,
  780. front_port=front_port_template,
  781. front_port_position=1,
  782. rear_port=rear_port_template,
  783. rear_port_position=i,
  784. )
  785. # Install the module
  786. module = Module.objects.create(
  787. device=device,
  788. module_bay=module_bay,
  789. module_type=module_type_with_mappings,
  790. status=ModuleStatusChoices.STATUS_ACTIVE,
  791. )
  792. # Verify that front ports were created
  793. front_ports = FrontPort.objects.filter(device=device, module=module)
  794. self.assertEqual(front_ports.count(), 12)
  795. # Verify that the rear port was created
  796. rear_ports = RearPort.objects.filter(device=device, module=module)
  797. self.assertEqual(rear_ports.count(), 1)
  798. rear_port = rear_ports.first()
  799. self.assertEqual(rear_port.positions, 12)
  800. # Verify that port mappings were created
  801. port_mappings = PortMapping.objects.filter(front_port__module=module)
  802. self.assertEqual(port_mappings.count(), 12)
  803. # Verify each mapping is correct
  804. for i, front_port_template in enumerate(front_port_templates, start=1):
  805. front_port = FrontPort.objects.get(
  806. device=device,
  807. name=front_port_template.name,
  808. module=module,
  809. )
  810. # Check that a mapping exists for this front port
  811. mapping = PortMapping.objects.get(
  812. device=device,
  813. front_port=front_port,
  814. front_port_position=1,
  815. )
  816. self.assertEqual(mapping.rear_port, rear_port)
  817. self.assertEqual(mapping.front_port_position, 1)
  818. self.assertEqual(mapping.rear_port_position, i)
  819. def test_module_installation_without_mappings(self):
  820. """
  821. Test that installing a module without port template mappings
  822. doesn't create any PortMapping instances.
  823. """
  824. device = Device.objects.first()
  825. manufacturer = Manufacturer.objects.first()
  826. module_bay = ModuleBay.objects.create(device=device, name='Test Bay PortMapping 2')
  827. # Create a module type without any port template mappings
  828. module_type_no_mappings = ModuleType.objects.create(
  829. manufacturer=manufacturer,
  830. model='Module Type Without Mappings',
  831. )
  832. # Create a rear port template
  833. RearPortTemplate.objects.create(
  834. module_type=module_type_no_mappings,
  835. name='Rear Port 1',
  836. type=PortTypeChoices.TYPE_SPLICE,
  837. positions=12,
  838. )
  839. # Create front port templates but DO NOT create PortTemplateMapping rows
  840. for i in range(1, 13):
  841. FrontPortTemplate.objects.create(
  842. module_type=module_type_no_mappings,
  843. name=f'port {i}',
  844. type=PortTypeChoices.TYPE_LC,
  845. positions=1,
  846. )
  847. # Install the module
  848. module = Module.objects.create(
  849. device=device,
  850. module_bay=module_bay,
  851. module_type=module_type_no_mappings,
  852. status=ModuleStatusChoices.STATUS_ACTIVE,
  853. )
  854. # Verify no port mappings were created for this module
  855. port_mappings = PortMapping.objects.filter(
  856. device=device,
  857. front_port__module=module,
  858. front_port_position=1,
  859. )
  860. self.assertEqual(port_mappings.count(), 0)
  861. self.assertEqual(FrontPort.objects.filter(module=module).count(), 12)
  862. self.assertEqual(RearPort.objects.filter(module=module).count(), 1)
  863. self.assertEqual(PortMapping.objects.filter(front_port__module=module).count(), 0)
  864. class CableTestCase(TestCase):
  865. @classmethod
  866. def setUpTestData(cls):
  867. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  868. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  869. devicetype = DeviceType.objects.create(
  870. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  871. )
  872. role = DeviceRole.objects.create(
  873. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  874. )
  875. device1 = Device.objects.create(
  876. device_type=devicetype, role=role, name='TestDevice1', site=site
  877. )
  878. device2 = Device.objects.create(
  879. device_type=devicetype, role=role, name='TestDevice2', site=site
  880. )
  881. interfaces = (
  882. Interface(device=device1, name='eth0'),
  883. Interface(device=device2, name='eth0'),
  884. Interface(device=device2, name='eth1'),
  885. )
  886. Interface.objects.bulk_create(interfaces)
  887. Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[1]]).save()
  888. PowerPort.objects.create(device=device2, name='psu1')
  889. patch_panel = Device.objects.create(
  890. device_type=devicetype, role=role, name='TestPatchPanel', site=site
  891. )
  892. rear_ports = (
  893. RearPort(device=patch_panel, name='RP1', type='8p8c'),
  894. RearPort(device=patch_panel, name='RP2', type='8p8c', positions=2),
  895. RearPort(device=patch_panel, name='RP3', type='8p8c', positions=3),
  896. RearPort(device=patch_panel, name='RP4', type='8p8c', positions=3),
  897. )
  898. RearPort.objects.bulk_create(rear_ports)
  899. front_ports = (
  900. FrontPort(device=patch_panel, name='FP1', type='8p8c'),
  901. FrontPort(device=patch_panel, name='FP2', type='8p8c'),
  902. FrontPort(device=patch_panel, name='FP3', type='8p8c'),
  903. FrontPort(device=patch_panel, name='FP4', type='8p8c'),
  904. )
  905. FrontPort.objects.bulk_create(front_ports)
  906. PortMapping.objects.bulk_create([
  907. PortMapping(device=patch_panel, front_port=front_ports[0], rear_port=rear_ports[0]),
  908. PortMapping(device=patch_panel, front_port=front_ports[1], rear_port=rear_ports[1]),
  909. PortMapping(device=patch_panel, front_port=front_ports[2], rear_port=rear_ports[2]),
  910. PortMapping(device=patch_panel, front_port=front_ports[3], rear_port=rear_ports[3]),
  911. ])
  912. provider = Provider.objects.create(name='Provider 1', slug='provider-1')
  913. provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider)
  914. circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
  915. circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1')
  916. circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2')
  917. CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='A')
  918. CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='Z')
  919. CircuitTermination.objects.create(circuit=circuit2, termination=provider_network, term_side='A')
  920. def test_cable_creation(self):
  921. """
  922. When a new Cable is created, it must be cached on either termination point.
  923. """
  924. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  925. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  926. cable = Cable.objects.first()
  927. self.assertEqual(interface1.cable, cable)
  928. self.assertEqual(interface2.cable, cable)
  929. self.assertEqual(interface1.cable_end, 'A')
  930. self.assertEqual(interface2.cable_end, 'B')
  931. self.assertEqual(interface1.link_peers, [interface2])
  932. self.assertEqual(interface2.link_peers, [interface1])
  933. def test_cable_deletion(self):
  934. """
  935. When a Cable is deleted, the `cable` field on its termination points must be nullified. The str() method
  936. should still return the PK of the string even after being nullified.
  937. """
  938. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  939. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  940. cable = Cable.objects.first()
  941. cable.delete()
  942. self.assertIsNone(cable.pk)
  943. self.assertNotEqual(str(cable), '#None')
  944. interface1 = Interface.objects.get(pk=interface1.pk)
  945. self.assertIsNone(interface1.cable)
  946. self.assertListEqual(interface1.link_peers, [])
  947. interface2 = Interface.objects.get(pk=interface2.pk)
  948. self.assertIsNone(interface2.cable)
  949. self.assertListEqual(interface2.link_peers, [])
  950. def test_cable_validates_same_parent_object(self):
  951. """
  952. The clean method should ensure that all terminations at either end of a Cable belong to the same parent object.
  953. """
  954. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  955. powerport1 = PowerPort.objects.get(device__name='TestDevice2', name='psu1')
  956. cable = Cable(a_terminations=[interface1], b_terminations=[powerport1])
  957. with self.assertRaises(ValidationError):
  958. cable.clean()
  959. def test_cable_validates_same_type(self):
  960. """
  961. The clean method should ensure that all terminations at either end of a Cable are of the same type.
  962. """
  963. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  964. frontport1 = FrontPort.objects.get(device__name='TestPatchPanel', name='FP1')
  965. rearport1 = RearPort.objects.get(device__name='TestPatchPanel', name='RP1')
  966. cable = Cable(a_terminations=[frontport1, rearport1], b_terminations=[interface1])
  967. with self.assertRaises(ValidationError):
  968. cable.clean()
  969. def test_cable_validates_compatible_types(self):
  970. """
  971. The clean method should have a check to ensure only compatible port types can be connected by a cable
  972. """
  973. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  974. powerport1 = PowerPort.objects.get(device__name='TestDevice2', name='psu1')
  975. # An interface cannot be connected to a power port, for example
  976. cable = Cable(a_terminations=[interface1], b_terminations=[powerport1])
  977. with self.assertRaises(ValidationError):
  978. cable.clean()
  979. def test_cable_cannot_terminate_to_a_provider_network_circuittermination(self):
  980. """
  981. Neither side of a cable can be terminated to a CircuitTermination which is attached to a ProviderNetwork
  982. """
  983. interface3 = Interface.objects.get(device__name='TestDevice2', name='eth1')
  984. circuittermination3 = CircuitTermination.objects.get(circuit__cid='2', term_side='A')
  985. cable = Cable(a_terminations=[interface3], b_terminations=[circuittermination3])
  986. with self.assertRaises(ValidationError):
  987. cable.clean()
  988. def test_cable_cannot_terminate_to_a_virtual_interface(self):
  989. """
  990. A cable cannot terminate to a virtual interface
  991. """
  992. device1 = Device.objects.get(name='TestDevice1')
  993. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  994. virtual_interface = Interface(device=device1, name="V1", type=InterfaceTypeChoices.TYPE_VIRTUAL)
  995. cable = Cable(a_terminations=[interface2], b_terminations=[virtual_interface])
  996. with self.assertRaises(ValidationError):
  997. cable.clean()
  998. def test_cable_cannot_terminate_to_a_wireless_interface(self):
  999. """
  1000. A cable cannot terminate to a wireless interface
  1001. """
  1002. device1 = Device.objects.get(name='TestDevice1')
  1003. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  1004. wireless_interface = Interface(device=device1, name="W1", type=InterfaceTypeChoices.TYPE_80211A)
  1005. cable = Cable(a_terminations=[interface2], b_terminations=[wireless_interface])
  1006. with self.assertRaises(ValidationError):
  1007. cable.clean()
  1008. @tag('regression')
  1009. def test_cable_cannot_terminate_to_a_cellular_interface(self):
  1010. """
  1011. A cable cannot terminate to a cellular interface
  1012. """
  1013. device1 = Device.objects.get(name='TestDevice1')
  1014. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  1015. cellular_interface = Interface(device=device1, name="W1", type=InterfaceTypeChoices.TYPE_LTE)
  1016. cable = Cable(a_terminations=[interface2], b_terminations=[cellular_interface])
  1017. with self.assertRaises(ValidationError):
  1018. cable.clean()
  1019. def test_cannot_cable_to_mark_connected(self):
  1020. """
  1021. Test that a cable cannot be connected to an interface marked as connected.
  1022. """
  1023. device1 = Device.objects.get(name='TestDevice1')
  1024. interface1 = Interface.objects.get(device__name='TestDevice2', name='eth1')
  1025. mark_connected_interface = Interface(device=device1, name='mark_connected1', mark_connected=True)
  1026. cable = Cable(a_terminations=[mark_connected_interface], b_terminations=[interface1])
  1027. with self.assertRaises(ValidationError):
  1028. cable.clean()
  1029. def test_cable_profile_change_preserves_terminations(self):
  1030. """
  1031. When a Cable's profile is changed via save() without explicitly setting terminations (as happens during
  1032. bulk edit), the existing termination points must be preserved.
  1033. """
  1034. cable = Cable.objects.first()
  1035. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  1036. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  1037. # Verify initial state: cable has terminations and no profile
  1038. self.assertEqual(cable.profile, '')
  1039. self.assertEqual(CableTermination.objects.filter(cable=cable).count(), 2)
  1040. # Simulate what bulk edit does: load the cable from DB, set profile via setattr, and save.
  1041. # Crucially, do NOT set a_terminations or b_terminations on the instance.
  1042. cable_from_db = Cable.objects.get(pk=cable.pk)
  1043. cable_from_db.profile = CableProfileChoices.SINGLE_1C1P
  1044. cable_from_db.save()
  1045. # Verify terminations are preserved
  1046. self.assertEqual(CableTermination.objects.filter(cable=cable).count(), 2)
  1047. # Verify the correct interfaces are still terminated
  1048. cable_from_db.refresh_from_db()
  1049. a_terms = [ct.termination for ct in CableTermination.objects.filter(cable=cable, cable_end='A')]
  1050. b_terms = [ct.termination for ct in CableTermination.objects.filter(cable=cable, cable_end='B')]
  1051. self.assertEqual(a_terms, [interface1])
  1052. self.assertEqual(b_terms, [interface2])
  1053. class VirtualDeviceContextTestCase(TestCase):
  1054. @classmethod
  1055. def setUpTestData(cls):
  1056. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  1057. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1058. devicetype = DeviceType.objects.create(
  1059. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  1060. )
  1061. role = DeviceRole.objects.create(
  1062. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  1063. )
  1064. Device.objects.create(
  1065. device_type=devicetype, role=role, name='TestDevice1', site=site
  1066. )
  1067. def test_vdc_and_interface_creation(self):
  1068. device = Device.objects.first()
  1069. vdc = VirtualDeviceContext(device=device, name="VDC 1", identifier=1, status='active')
  1070. vdc.full_clean()
  1071. vdc.save()
  1072. interface = Interface(device=device, name='Eth1/1', type='10gbase-t')
  1073. interface.full_clean()
  1074. interface.save()
  1075. interface.vdcs.set([vdc])
  1076. def test_vdc_duplicate_name(self):
  1077. device = Device.objects.first()
  1078. vdc1 = VirtualDeviceContext(device=device, name="VDC 1", identifier=1, status='active')
  1079. vdc1.full_clean()
  1080. vdc1.save()
  1081. vdc2 = VirtualDeviceContext(device=device, name="VDC 1", identifier=2, status='active')
  1082. with self.assertRaises(ValidationError):
  1083. vdc2.full_clean()
  1084. def test_vdc_duplicate_identifier(self):
  1085. device = Device.objects.first()
  1086. vdc1 = VirtualDeviceContext(device=device, name="VDC 1", identifier=1, status='active')
  1087. vdc1.full_clean()
  1088. vdc1.save()
  1089. vdc2 = VirtualDeviceContext(device=device, name="VDC 2", identifier=1, status='active')
  1090. with self.assertRaises(ValidationError):
  1091. vdc2.full_clean()
  1092. class VirtualChassisTestCase(TestCase):
  1093. @classmethod
  1094. def setUpTestData(cls):
  1095. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  1096. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1097. devicetype = DeviceType.objects.create(
  1098. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  1099. )
  1100. role = DeviceRole.objects.create(
  1101. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  1102. )
  1103. Device.objects.create(
  1104. device_type=devicetype, role=role, name='TestDevice1', site=site
  1105. )
  1106. Device.objects.create(
  1107. device_type=devicetype, role=role, name='TestDevice2', site=site
  1108. )
  1109. def test_virtualchassis_deletion_clears_vc_position(self):
  1110. """
  1111. Test that when a VirtualChassis is deleted, member devices have their
  1112. vc_position and vc_priority fields set to None.
  1113. """
  1114. devices = Device.objects.all()
  1115. device1 = devices[0]
  1116. device2 = devices[1]
  1117. # Create a VirtualChassis with two member devices
  1118. vc = VirtualChassis.objects.create(name='Test VC', master=device1)
  1119. device1.virtual_chassis = vc
  1120. device1.vc_position = 1
  1121. device1.vc_priority = 10
  1122. device1.save()
  1123. device2.virtual_chassis = vc
  1124. device2.vc_position = 2
  1125. device2.vc_priority = 20
  1126. device2.save()
  1127. # Verify devices are members of the VC with positions set
  1128. device1.refresh_from_db()
  1129. device2.refresh_from_db()
  1130. self.assertEqual(device1.virtual_chassis, vc)
  1131. self.assertEqual(device1.vc_position, 1)
  1132. self.assertEqual(device1.vc_priority, 10)
  1133. self.assertEqual(device2.virtual_chassis, vc)
  1134. self.assertEqual(device2.vc_position, 2)
  1135. self.assertEqual(device2.vc_priority, 20)
  1136. # Delete the VirtualChassis
  1137. vc.delete()
  1138. # Verify devices have vc_position and vc_priority set to None
  1139. device1.refresh_from_db()
  1140. device2.refresh_from_db()
  1141. self.assertIsNone(device1.virtual_chassis)
  1142. self.assertIsNone(device1.vc_position)
  1143. self.assertIsNone(device1.vc_priority)
  1144. self.assertIsNone(device2.virtual_chassis)
  1145. self.assertIsNone(device2.vc_position)
  1146. self.assertIsNone(device2.vc_priority)
  1147. def test_virtualchassis_duplicate_vc_position(self):
  1148. """
  1149. Test that two devices cannot be assigned to the same vc_position
  1150. within the same VirtualChassis.
  1151. """
  1152. devices = Device.objects.all()
  1153. device1 = devices[0]
  1154. device2 = devices[1]
  1155. # Create a VirtualChassis
  1156. vc = VirtualChassis.objects.create(name='Test VC')
  1157. # Assign first device to vc_position 1
  1158. device1.virtual_chassis = vc
  1159. device1.vc_position = 1
  1160. device1.full_clean()
  1161. device1.save()
  1162. # Try to assign second device to the same vc_position
  1163. device2.virtual_chassis = vc
  1164. device2.vc_position = 1
  1165. with self.assertRaises(ValidationError):
  1166. device2.full_clean()
  1167. class SiteSignalTestCase(TestCase):
  1168. @tag('regression')
  1169. def test_edit_site_with_prefix_no_vrf(self):
  1170. site = Site.objects.create(name='Test Site', slug='test-site')
  1171. Prefix.objects.create(prefix='192.0.2.0/24', scope=site, vrf=None)
  1172. # Regression test for #21045: should not raise ValueError
  1173. site.save()