test_models.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. from django.core.exceptions import ValidationError
  2. from django.test import TestCase
  3. from dcim.choices import *
  4. from dcim.constants import CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_PLANNED
  5. from dcim.models import *
  6. from tenancy.models import Tenant
  7. class RackTestCase(TestCase):
  8. def setUp(self):
  9. self.site1 = Site.objects.create(
  10. name='TestSite1',
  11. slug='test-site-1'
  12. )
  13. self.site2 = Site.objects.create(
  14. name='TestSite2',
  15. slug='test-site-2'
  16. )
  17. self.group1 = RackGroup.objects.create(
  18. name='TestGroup1',
  19. slug='test-group-1',
  20. site=self.site1
  21. )
  22. self.group2 = RackGroup.objects.create(
  23. name='TestGroup2',
  24. slug='test-group-2',
  25. site=self.site2
  26. )
  27. self.rack = Rack.objects.create(
  28. name='TestRack1',
  29. facility_id='A101',
  30. site=self.site1,
  31. group=self.group1,
  32. u_height=42
  33. )
  34. self.manufacturer = Manufacturer.objects.create(
  35. name='Acme',
  36. slug='acme'
  37. )
  38. self.device_type = {
  39. 'ff2048': DeviceType.objects.create(
  40. manufacturer=self.manufacturer,
  41. model='FrameForwarder 2048',
  42. slug='ff2048'
  43. ),
  44. 'cc5000': DeviceType.objects.create(
  45. manufacturer=self.manufacturer,
  46. model='CurrentCatapult 5000',
  47. slug='cc5000',
  48. u_height=0
  49. ),
  50. }
  51. self.role = {
  52. 'Server': DeviceRole.objects.create(
  53. name='Server',
  54. slug='server',
  55. ),
  56. 'Switch': DeviceRole.objects.create(
  57. name='Switch',
  58. slug='switch',
  59. ),
  60. 'Console Server': DeviceRole.objects.create(
  61. name='Console Server',
  62. slug='console-server',
  63. ),
  64. 'PDU': DeviceRole.objects.create(
  65. name='PDU',
  66. slug='pdu',
  67. ),
  68. }
  69. def test_rack_device_outside_height(self):
  70. rack1 = Rack(
  71. name='TestRack2',
  72. facility_id='A102',
  73. site=self.site1,
  74. u_height=42
  75. )
  76. rack1.save()
  77. device1 = Device(
  78. name='TestSwitch1',
  79. device_type=DeviceType.objects.get(manufacturer__slug='acme', slug='ff2048'),
  80. device_role=DeviceRole.objects.get(slug='switch'),
  81. site=self.site1,
  82. rack=rack1,
  83. position=43,
  84. face=DeviceFaceChoices.FACE_FRONT,
  85. )
  86. device1.save()
  87. with self.assertRaises(ValidationError):
  88. rack1.clean()
  89. def test_rack_group_site(self):
  90. rack_invalid_group = Rack(
  91. name='TestRack2',
  92. facility_id='A102',
  93. site=self.site1,
  94. u_height=42,
  95. group=self.group2
  96. )
  97. rack_invalid_group.save()
  98. with self.assertRaises(ValidationError):
  99. rack_invalid_group.clean()
  100. def test_mount_single_device(self):
  101. device1 = Device(
  102. name='TestSwitch1',
  103. device_type=DeviceType.objects.get(manufacturer__slug='acme', slug='ff2048'),
  104. device_role=DeviceRole.objects.get(slug='switch'),
  105. site=self.site1,
  106. rack=self.rack,
  107. position=10,
  108. face=DeviceFaceChoices.FACE_REAR,
  109. )
  110. device1.save()
  111. # Validate rack height
  112. self.assertEqual(list(self.rack.units), list(reversed(range(1, 43))))
  113. # Validate inventory (front face)
  114. rack1_inventory_front = self.rack.get_rack_units(face=DeviceFaceChoices.FACE_FRONT)
  115. self.assertEqual(rack1_inventory_front[-10]['device'], device1)
  116. del(rack1_inventory_front[-10])
  117. for u in rack1_inventory_front:
  118. self.assertIsNone(u['device'])
  119. # Validate inventory (rear face)
  120. rack1_inventory_rear = self.rack.get_rack_units(face=DeviceFaceChoices.FACE_REAR)
  121. self.assertEqual(rack1_inventory_rear[-10]['device'], device1)
  122. del(rack1_inventory_rear[-10])
  123. for u in rack1_inventory_rear:
  124. self.assertIsNone(u['device'])
  125. def test_mount_zero_ru(self):
  126. pdu = Device.objects.create(
  127. name='TestPDU',
  128. device_role=self.role.get('PDU'),
  129. device_type=self.device_type.get('cc5000'),
  130. site=self.site1,
  131. rack=self.rack,
  132. position=None,
  133. face='',
  134. )
  135. self.assertTrue(pdu)
  136. class DeviceTestCase(TestCase):
  137. def setUp(self):
  138. self.site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  139. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  140. self.device_type = DeviceType.objects.create(
  141. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  142. )
  143. self.device_role = DeviceRole.objects.create(
  144. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  145. )
  146. # Create DeviceType components
  147. ConsolePortTemplate(
  148. device_type=self.device_type,
  149. name='Console Port 1'
  150. ).save()
  151. ConsoleServerPortTemplate(
  152. device_type=self.device_type,
  153. name='Console Server Port 1'
  154. ).save()
  155. ppt = PowerPortTemplate(
  156. device_type=self.device_type,
  157. name='Power Port 1',
  158. maximum_draw=1000,
  159. allocated_draw=500
  160. )
  161. ppt.save()
  162. PowerOutletTemplate(
  163. device_type=self.device_type,
  164. name='Power Outlet 1',
  165. power_port=ppt,
  166. feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
  167. ).save()
  168. InterfaceTemplate(
  169. device_type=self.device_type,
  170. name='Interface 1',
  171. type=InterfaceTypeChoices.TYPE_1GE_FIXED,
  172. mgmt_only=True
  173. ).save()
  174. rpt = RearPortTemplate(
  175. device_type=self.device_type,
  176. name='Rear Port 1',
  177. type=PortTypeChoices.TYPE_8P8C,
  178. positions=8
  179. )
  180. rpt.save()
  181. FrontPortTemplate(
  182. device_type=self.device_type,
  183. name='Front Port 1',
  184. type=PortTypeChoices.TYPE_8P8C,
  185. rear_port=rpt,
  186. rear_port_position=2
  187. ).save()
  188. DeviceBayTemplate(
  189. device_type=self.device_type,
  190. name='Device Bay 1'
  191. ).save()
  192. def test_device_creation(self):
  193. """
  194. Ensure that all Device components are copied automatically from the DeviceType.
  195. """
  196. d = Device(
  197. site=self.site,
  198. device_type=self.device_type,
  199. device_role=self.device_role,
  200. name='Test Device 1'
  201. )
  202. d.save()
  203. ConsolePort.objects.get(
  204. device=d,
  205. name='Console Port 1'
  206. )
  207. ConsoleServerPort.objects.get(
  208. device=d,
  209. name='Console Server Port 1'
  210. )
  211. pp = PowerPort.objects.get(
  212. device=d,
  213. name='Power Port 1',
  214. maximum_draw=1000,
  215. allocated_draw=500
  216. )
  217. PowerOutlet.objects.get(
  218. device=d,
  219. name='Power Outlet 1',
  220. power_port=pp,
  221. feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A
  222. )
  223. Interface.objects.get(
  224. device=d,
  225. name='Interface 1',
  226. type=InterfaceTypeChoices.TYPE_1GE_FIXED,
  227. mgmt_only=True
  228. )
  229. rp = RearPort.objects.get(
  230. device=d,
  231. name='Rear Port 1',
  232. type=PortTypeChoices.TYPE_8P8C,
  233. positions=8
  234. )
  235. FrontPort.objects.get(
  236. device=d,
  237. name='Front Port 1',
  238. type=PortTypeChoices.TYPE_8P8C,
  239. rear_port=rp,
  240. rear_port_position=2
  241. )
  242. DeviceBay.objects.get(
  243. device=d,
  244. name='Device Bay 1'
  245. )
  246. def test_multiple_unnamed_devices(self):
  247. device1 = Device(
  248. site=self.site,
  249. device_type=self.device_type,
  250. device_role=self.device_role,
  251. name=''
  252. )
  253. device1.save()
  254. device2 = Device(
  255. site=device1.site,
  256. device_type=device1.device_type,
  257. device_role=device1.device_role,
  258. name=''
  259. )
  260. device2.full_clean()
  261. device2.save()
  262. self.assertEqual(Device.objects.filter(name='').count(), 2)
  263. def test_device_duplicate_names(self):
  264. device1 = Device(
  265. site=self.site,
  266. device_type=self.device_type,
  267. device_role=self.device_role,
  268. name='Test Device 1'
  269. )
  270. device1.save()
  271. device2 = Device(
  272. site=device1.site,
  273. device_type=device1.device_type,
  274. device_role=device1.device_role,
  275. name=device1.name
  276. )
  277. # Two devices assigned to the same Site and no Tenant should fail validation
  278. with self.assertRaises(ValidationError):
  279. device2.full_clean()
  280. tenant = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
  281. device1.tenant = tenant
  282. device1.save()
  283. device2.tenant = tenant
  284. # Two devices assigned to the same Site and the same Tenant should fail validation
  285. with self.assertRaises(ValidationError):
  286. device2.full_clean()
  287. device2.tenant = None
  288. # Two devices assigned to the same Site and different Tenants should pass validation
  289. device2.full_clean()
  290. device2.save()
  291. class CableTestCase(TestCase):
  292. def setUp(self):
  293. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  294. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  295. devicetype = DeviceType.objects.create(
  296. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  297. )
  298. devicerole = DeviceRole.objects.create(
  299. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  300. )
  301. self.device1 = Device.objects.create(
  302. device_type=devicetype, device_role=devicerole, name='TestDevice1', site=site
  303. )
  304. self.device2 = Device.objects.create(
  305. device_type=devicetype, device_role=devicerole, name='TestDevice2', site=site
  306. )
  307. self.interface1 = Interface.objects.create(device=self.device1, name='eth0')
  308. self.interface2 = Interface.objects.create(device=self.device2, name='eth0')
  309. self.cable = Cable(termination_a=self.interface1, termination_b=self.interface2)
  310. self.cable.save()
  311. self.power_port1 = PowerPort.objects.create(device=self.device2, name='psu1')
  312. self.patch_pannel = Device.objects.create(
  313. device_type=devicetype, device_role=devicerole, name='TestPatchPannel', site=site
  314. )
  315. self.rear_port = RearPort.objects.create(device=self.patch_pannel, name='R1', type=1000)
  316. self.front_port = FrontPort.objects.create(
  317. device=self.patch_pannel, name='F1', type=1000, rear_port=self.rear_port
  318. )
  319. def test_cable_creation(self):
  320. """
  321. When a new Cable is created, it must be cached on either termination point.
  322. """
  323. interface1 = Interface.objects.get(pk=self.interface1.pk)
  324. self.assertEqual(self.cable.termination_a, interface1)
  325. interface2 = Interface.objects.get(pk=self.interface2.pk)
  326. self.assertEqual(self.cable.termination_b, interface2)
  327. def test_cable_deletion(self):
  328. """
  329. When a Cable is deleted, the `cable` field on its termination points must be nullified. The str() method
  330. should still return the PK of the string even after being nullified.
  331. """
  332. self.cable.delete()
  333. self.assertIsNone(self.cable.pk)
  334. self.assertNotEqual(str(self.cable), '#None')
  335. interface1 = Interface.objects.get(pk=self.interface1.pk)
  336. self.assertIsNone(interface1.cable)
  337. interface2 = Interface.objects.get(pk=self.interface2.pk)
  338. self.assertIsNone(interface2.cable)
  339. def test_cabletermination_deletion(self):
  340. """
  341. When a CableTermination object is deleted, its attached Cable (if any) must also be deleted.
  342. """
  343. self.interface1.delete()
  344. cable = Cable.objects.filter(pk=self.cable.pk).first()
  345. self.assertIsNone(cable)
  346. def test_cable_validates_compatibale_types(self):
  347. """
  348. The clean method should have a check to ensure only compatible port types can be connected by a cable
  349. """
  350. # An interface cannot be connected to a power port
  351. cable = Cable(termination_a=self.interface1, termination_b=self.power_port1)
  352. with self.assertRaises(ValidationError):
  353. cable.clean()
  354. def test_cable_cannot_have_the_same_terminination_on_both_ends(self):
  355. """
  356. A cable cannot be made with the same A and B side terminations
  357. """
  358. cable = Cable(termination_a=self.interface1, termination_b=self.interface1)
  359. with self.assertRaises(ValidationError):
  360. cable.clean()
  361. def test_cable_front_port_cannot_connect_to_corresponding_rear_port(self):
  362. """
  363. A cable cannot connect a front port to its corresponding rear port
  364. """
  365. cable = Cable(termination_a=self.front_port, termination_b=self.rear_port)
  366. with self.assertRaises(ValidationError):
  367. cable.clean()
  368. def test_cable_cannot_terminate_to_an_existing_connection(self):
  369. """
  370. Either side of a cable cannot be terminated when that side already has a connection
  371. """
  372. # Try to create a cable with the same interface terminations
  373. cable = Cable(termination_a=self.interface2, termination_b=self.interface1)
  374. with self.assertRaises(ValidationError):
  375. cable.clean()
  376. def test_cable_cannot_terminate_to_a_virtual_inteface(self):
  377. """
  378. A cable cannot terminate to a virtual interface
  379. """
  380. virtual_interface = Interface(device=self.device1, name="V1", type=InterfaceTypeChoices.TYPE_VIRTUAL)
  381. cable = Cable(termination_a=self.interface2, termination_b=virtual_interface)
  382. with self.assertRaises(ValidationError):
  383. cable.clean()
  384. def test_cable_cannot_terminate_to_a_wireless_inteface(self):
  385. """
  386. A cable cannot terminate to a wireless interface
  387. """
  388. wireless_interface = Interface(device=self.device1, name="W1", type=InterfaceTypeChoices.TYPE_80211A)
  389. cable = Cable(termination_a=self.interface2, termination_b=wireless_interface)
  390. with self.assertRaises(ValidationError):
  391. cable.clean()
  392. class CablePathTestCase(TestCase):
  393. def setUp(self):
  394. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  395. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  396. devicetype = DeviceType.objects.create(
  397. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  398. )
  399. devicerole = DeviceRole.objects.create(
  400. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  401. )
  402. self.device1 = Device.objects.create(
  403. device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
  404. )
  405. self.device2 = Device.objects.create(
  406. device_type=devicetype, device_role=devicerole, name='Test Device 2', site=site
  407. )
  408. self.interface1 = Interface.objects.create(device=self.device1, name='eth0')
  409. self.interface2 = Interface.objects.create(device=self.device2, name='eth0')
  410. self.panel1 = Device.objects.create(
  411. device_type=devicetype, device_role=devicerole, name='Test Panel 1', site=site
  412. )
  413. self.panel2 = Device.objects.create(
  414. device_type=devicetype, device_role=devicerole, name='Test Panel 2', site=site
  415. )
  416. self.rear_port1 = RearPort.objects.create(
  417. device=self.panel1, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C
  418. )
  419. self.front_port1 = FrontPort.objects.create(
  420. device=self.panel1, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=self.rear_port1
  421. )
  422. self.rear_port2 = RearPort.objects.create(
  423. device=self.panel2, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C
  424. )
  425. self.front_port2 = FrontPort.objects.create(
  426. device=self.panel2, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=self.rear_port2
  427. )
  428. def test_path_completion(self):
  429. # First segment
  430. cable1 = Cable(termination_a=self.interface1, termination_b=self.front_port1)
  431. cable1.save()
  432. interface1 = Interface.objects.get(pk=self.interface1.pk)
  433. self.assertIsNone(interface1.connected_endpoint)
  434. self.assertIsNone(interface1.connection_status)
  435. # Second segment
  436. cable2 = Cable(termination_a=self.rear_port1, termination_b=self.rear_port2)
  437. cable2.save()
  438. interface1 = Interface.objects.get(pk=self.interface1.pk)
  439. self.assertIsNone(interface1.connected_endpoint)
  440. self.assertIsNone(interface1.connection_status)
  441. # Third segment
  442. cable3 = Cable(
  443. termination_a=self.front_port2,
  444. termination_b=self.interface2,
  445. status=CableStatusChoices.STATUS_PLANNED
  446. )
  447. cable3.save()
  448. interface1 = Interface.objects.get(pk=self.interface1.pk)
  449. self.assertEqual(interface1.connected_endpoint, self.interface2)
  450. self.assertEqual(interface1.connection_status, CONNECTION_STATUS_PLANNED)
  451. # Switch third segment from planned to connected
  452. cable3.status = CableStatusChoices.STATUS_CONNECTED
  453. cable3.save()
  454. interface1 = Interface.objects.get(pk=self.interface1.pk)
  455. self.assertEqual(interface1.connected_endpoint, self.interface2)
  456. self.assertEqual(interface1.connection_status, CONNECTION_STATUS_CONNECTED)
  457. def test_path_teardown(self):
  458. # Build the path
  459. cable1 = Cable(termination_a=self.interface1, termination_b=self.front_port1)
  460. cable1.save()
  461. cable2 = Cable(termination_a=self.rear_port1, termination_b=self.rear_port2)
  462. cable2.save()
  463. cable3 = Cable(termination_a=self.front_port2, termination_b=self.interface2)
  464. cable3.save()
  465. interface1 = Interface.objects.get(pk=self.interface1.pk)
  466. self.assertEqual(interface1.connected_endpoint, self.interface2)
  467. self.assertEqual(interface1.connection_status, CONNECTION_STATUS_CONNECTED)
  468. # Remove a cable
  469. cable2.delete()
  470. interface1 = Interface.objects.get(pk=self.interface1.pk)
  471. self.assertIsNone(interface1.connected_endpoint)
  472. self.assertIsNone(interface1.connection_status)
  473. interface2 = Interface.objects.get(pk=self.interface2.pk)
  474. self.assertIsNone(interface2.connected_endpoint)
  475. self.assertIsNone(interface2.connection_status)