test_models.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. from django.core.exceptions import ValidationError
  2. from django.test import tag, TestCase
  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. FrontPortTemplate(
  382. device_type=device_type,
  383. name='Front Port 1',
  384. type=PortTypeChoices.TYPE_8P8C,
  385. rear_port=rearport,
  386. rear_port_position=2
  387. ).save()
  388. ModuleBayTemplate(
  389. device_type=device_type,
  390. name='Module Bay 1'
  391. ).save()
  392. DeviceBayTemplate(
  393. device_type=device_type,
  394. name='Device Bay 1'
  395. ).save()
  396. InventoryItemTemplate(
  397. device_type=device_type,
  398. name='Inventory Item 1'
  399. ).save()
  400. def test_device_creation(self):
  401. """
  402. Ensure that all Device components are copied automatically from the DeviceType.
  403. """
  404. device = Device(
  405. site=Site.objects.first(),
  406. device_type=DeviceType.objects.first(),
  407. role=DeviceRole.objects.first(),
  408. name='Test Device 1'
  409. )
  410. device.save()
  411. consoleport = ConsolePort.objects.get(
  412. device=device,
  413. name='Console Port 1'
  414. )
  415. self.assertEqual(consoleport.cf['cf1'], 'foo')
  416. consoleserverport = ConsoleServerPort.objects.get(
  417. device=device,
  418. name='Console Server Port 1'
  419. )
  420. self.assertEqual(consoleserverport.cf['cf1'], 'foo')
  421. powerport = PowerPort.objects.get(
  422. device=device,
  423. name='Power Port 1',
  424. maximum_draw=1000,
  425. allocated_draw=500
  426. )
  427. self.assertEqual(powerport.cf['cf1'], 'foo')
  428. poweroutlet = PowerOutlet.objects.get(
  429. device=device,
  430. name='Power Outlet 1',
  431. power_port=powerport,
  432. feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A,
  433. status=PowerOutletStatusChoices.STATUS_ENABLED,
  434. )
  435. self.assertEqual(poweroutlet.cf['cf1'], 'foo')
  436. interface = Interface.objects.get(
  437. device=device,
  438. name='Interface 1',
  439. type=InterfaceTypeChoices.TYPE_1GE_FIXED,
  440. mgmt_only=True
  441. )
  442. self.assertEqual(interface.cf['cf1'], 'foo')
  443. rearport = RearPort.objects.get(
  444. device=device,
  445. name='Rear Port 1',
  446. type=PortTypeChoices.TYPE_8P8C,
  447. positions=8
  448. )
  449. self.assertEqual(rearport.cf['cf1'], 'foo')
  450. frontport = FrontPort.objects.get(
  451. device=device,
  452. name='Front Port 1',
  453. type=PortTypeChoices.TYPE_8P8C,
  454. rear_port=rearport,
  455. rear_port_position=2
  456. )
  457. self.assertEqual(frontport.cf['cf1'], 'foo')
  458. modulebay = ModuleBay.objects.get(
  459. device=device,
  460. name='Module Bay 1'
  461. )
  462. self.assertEqual(modulebay.cf['cf1'], 'foo')
  463. devicebay = DeviceBay.objects.get(
  464. device=device,
  465. name='Device Bay 1'
  466. )
  467. self.assertEqual(devicebay.cf['cf1'], 'foo')
  468. inventoryitem = InventoryItem.objects.get(
  469. device=device,
  470. name='Inventory Item 1'
  471. )
  472. self.assertEqual(inventoryitem.cf['cf1'], 'foo')
  473. def test_multiple_unnamed_devices(self):
  474. device1 = Device(
  475. site=Site.objects.first(),
  476. device_type=DeviceType.objects.first(),
  477. role=DeviceRole.objects.first(),
  478. name=None
  479. )
  480. device1.save()
  481. device2 = Device(
  482. site=device1.site,
  483. device_type=device1.device_type,
  484. role=device1.role,
  485. name=None
  486. )
  487. device2.full_clean()
  488. device2.save()
  489. self.assertEqual(Device.objects.filter(name__isnull=True).count(), 2)
  490. def test_device_name_case_sensitivity(self):
  491. device1 = Device(
  492. site=Site.objects.first(),
  493. device_type=DeviceType.objects.first(),
  494. role=DeviceRole.objects.first(),
  495. name='device 1'
  496. )
  497. device1.save()
  498. device2 = Device(
  499. site=device1.site,
  500. device_type=device1.device_type,
  501. role=device1.role,
  502. name='DEVICE 1'
  503. )
  504. # Uniqueness validation for name should ignore case
  505. with self.assertRaises(ValidationError):
  506. device2.full_clean()
  507. def test_device_duplicate_names(self):
  508. device1 = Device(
  509. site=Site.objects.first(),
  510. device_type=DeviceType.objects.first(),
  511. role=DeviceRole.objects.first(),
  512. name='Test Device 1'
  513. )
  514. device1.save()
  515. device2 = Device(
  516. site=device1.site,
  517. device_type=device1.device_type,
  518. role=device1.role,
  519. name=device1.name
  520. )
  521. # Two devices assigned to the same Site and no Tenant should fail validation
  522. with self.assertRaises(ValidationError):
  523. device2.full_clean()
  524. tenant = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
  525. device1.tenant = tenant
  526. device1.save()
  527. device2.tenant = tenant
  528. # Two devices assigned to the same Site and the same Tenant should fail validation
  529. with self.assertRaises(ValidationError):
  530. device2.full_clean()
  531. device2.tenant = None
  532. # Two devices assigned to the same Site and different Tenants should pass validation
  533. device2.full_clean()
  534. device2.save()
  535. def test_device_label(self):
  536. device1 = Device(
  537. site=Site.objects.first(),
  538. device_type=DeviceType.objects.first(),
  539. role=DeviceRole.objects.first(),
  540. name=None,
  541. )
  542. self.assertEqual(device1.label, None)
  543. device1.name = 'Test Device 1'
  544. self.assertEqual(device1.label, 'Test Device 1')
  545. virtual_chassis = VirtualChassis.objects.create(name='VC 1')
  546. device2 = Device(
  547. site=Site.objects.first(),
  548. device_type=DeviceType.objects.first(),
  549. role=DeviceRole.objects.first(),
  550. name=None,
  551. virtual_chassis=virtual_chassis,
  552. vc_position=2,
  553. )
  554. self.assertEqual(device2.label, 'VC 1:2')
  555. device2.name = 'Test Device 2'
  556. self.assertEqual(device2.label, 'Test Device 2')
  557. def test_device_mismatched_site_cluster(self):
  558. cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
  559. Cluster.objects.create(name='Cluster 1', type=cluster_type)
  560. sites = (
  561. Site(name='Site 1', slug='site-1'),
  562. Site(name='Site 2', slug='site-2'),
  563. )
  564. Site.objects.bulk_create(sites)
  565. clusters = (
  566. Cluster(name='Cluster 1', type=cluster_type, scope=sites[0]),
  567. Cluster(name='Cluster 2', type=cluster_type, scope=sites[1]),
  568. Cluster(name='Cluster 3', type=cluster_type, scope=None),
  569. )
  570. for cluster in clusters:
  571. cluster.save()
  572. device_type = DeviceType.objects.first()
  573. device_role = DeviceRole.objects.first()
  574. # Device with site only should pass
  575. Device(
  576. name='device1',
  577. site=sites[0],
  578. device_type=device_type,
  579. role=device_role
  580. ).full_clean()
  581. # Device with site, cluster non-site should pass
  582. Device(
  583. name='device1',
  584. site=sites[0],
  585. device_type=device_type,
  586. role=device_role,
  587. cluster=clusters[2]
  588. ).full_clean()
  589. # Device with mismatched site & cluster should fail
  590. with self.assertRaises(ValidationError):
  591. Device(
  592. name='device1',
  593. site=sites[0],
  594. device_type=device_type,
  595. role=device_role,
  596. cluster=clusters[1]
  597. ).full_clean()
  598. class ModuleBayTestCase(TestCase):
  599. @classmethod
  600. def setUpTestData(cls):
  601. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  602. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  603. device_type = DeviceType.objects.create(
  604. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  605. )
  606. device_role = DeviceRole.objects.create(name='Test Role 1', slug='test-role-1')
  607. # Create a CustomField with a default value & assign it to all component models
  608. location = Location.objects.create(name='Location 1', slug='location-1', site=site)
  609. rack = Rack.objects.create(name='Rack 1', site=site)
  610. device = Device.objects.create(
  611. name='Device 1', device_type=device_type, role=device_role, site=site, location=location, rack=rack
  612. )
  613. module_bays = (
  614. ModuleBay(device=device, name='Module Bay 1', label='A', description='First'),
  615. ModuleBay(device=device, name='Module Bay 2', label='B', description='Second'),
  616. ModuleBay(device=device, name='Module Bay 3', label='C', description='Third'),
  617. )
  618. for module_bay in module_bays:
  619. module_bay.save()
  620. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  621. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
  622. modules = (
  623. Module(device=device, module_bay=module_bays[0], module_type=module_type),
  624. Module(device=device, module_bay=module_bays[1], module_type=module_type),
  625. Module(device=device, module_bay=module_bays[2], module_type=module_type),
  626. )
  627. # M3 -> MB3 -> M2 -> MB2 -> M1 -> MB1
  628. Module.objects.bulk_create(modules)
  629. module_bays[1].module = modules[0]
  630. module_bays[1].clean()
  631. module_bays[1].save()
  632. module_bays[2].module = modules[1]
  633. module_bays[2].clean()
  634. module_bays[2].save()
  635. def test_module_bay_recursion(self):
  636. module_bay_1 = ModuleBay.objects.get(name='Module Bay 1')
  637. module_bay_3 = ModuleBay.objects.get(name='Module Bay 3')
  638. module_1 = Module.objects.get(module_bay=module_bay_1)
  639. module_3 = Module.objects.get(module_bay=module_bay_3)
  640. # Confirm error if ModuleBay recurses
  641. with self.assertRaises(ValidationError):
  642. module_bay_1.module = module_3
  643. module_bay_1.clean()
  644. module_bay_1.save()
  645. # Confirm error if Module recurses
  646. with self.assertRaises(ValidationError):
  647. module_1.module_bay = module_bay_3
  648. module_1.clean()
  649. module_1.save()
  650. def test_single_module_token(self):
  651. device_type = DeviceType.objects.first()
  652. device_role = DeviceRole.objects.first()
  653. site = Site.objects.first()
  654. location = Location.objects.first()
  655. rack = Rack.objects.first()
  656. # Create DeviceType components
  657. ConsolePortTemplate.objects.create(
  658. device_type=device_type,
  659. name='{module}',
  660. label='{module}',
  661. )
  662. ModuleBayTemplate.objects.create(
  663. device_type=device_type,
  664. name='Module Bay 1'
  665. )
  666. device = Device.objects.create(
  667. name='Device 2',
  668. device_type=device_type,
  669. role=device_role,
  670. site=site,
  671. location=location,
  672. rack=rack
  673. )
  674. device.consoleports.first()
  675. @tag('regression') # #19918
  676. def test_nested_module_bay_label_resolution(self):
  677. """Test that nested module bay labels properly resolve {module} placeholders"""
  678. manufacturer = Manufacturer.objects.first()
  679. site = Site.objects.first()
  680. device_role = DeviceRole.objects.first()
  681. # Create device type with module bay template (position='A')
  682. device_type = DeviceType.objects.create(
  683. manufacturer=manufacturer,
  684. model='Device with Bays',
  685. slug='device-with-bays'
  686. )
  687. ModuleBayTemplate.objects.create(
  688. device_type=device_type,
  689. name='Bay A',
  690. position='A'
  691. )
  692. # Create module type with nested bay template using {module} placeholder
  693. module_type = ModuleType.objects.create(
  694. manufacturer=manufacturer,
  695. model='Module with Nested Bays'
  696. )
  697. ModuleBayTemplate.objects.create(
  698. module_type=module_type,
  699. name='SFP {module}-21',
  700. label='{module}-21',
  701. position='21'
  702. )
  703. # Create device and install module
  704. device = Device.objects.create(
  705. name='Test Device',
  706. device_type=device_type,
  707. role=device_role,
  708. site=site
  709. )
  710. module_bay = device.modulebays.get(name='Bay A')
  711. module = Module.objects.create(
  712. device=device,
  713. module_bay=module_bay,
  714. module_type=module_type
  715. )
  716. # Verify nested bay label resolves {module} to parent position
  717. nested_bay = module.modulebays.get(name='SFP A-21')
  718. self.assertEqual(nested_bay.label, 'A-21')
  719. @tag('regression') # #20912
  720. def test_module_bay_parent_cleared_when_module_removed(self):
  721. """Test that the parent field is properly cleared when a module bay's module assignment is removed"""
  722. device = Device.objects.first()
  723. manufacturer = Manufacturer.objects.first()
  724. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Test Module Type')
  725. bay1 = ModuleBay.objects.create(device=device, name='Test Bay 1')
  726. bay2 = ModuleBay.objects.create(device=device, name='Test Bay 2')
  727. # Install a module in bay1
  728. module1 = Module.objects.create(device=device, module_bay=bay1, module_type=module_type)
  729. # Assign bay2 to module1 and verify parent is now set to bay1 (module1's bay)
  730. bay2.module = module1
  731. bay2.save()
  732. bay2.refresh_from_db()
  733. self.assertEqual(bay2.parent, bay1)
  734. self.assertEqual(bay2.module, module1)
  735. # Clear the module assignment (return bay2 to device level) Verify parent is cleared
  736. bay2.module = None
  737. bay2.save()
  738. bay2.refresh_from_db()
  739. self.assertIsNone(bay2.parent)
  740. self.assertIsNone(bay2.module)
  741. class CableTestCase(TestCase):
  742. @classmethod
  743. def setUpTestData(cls):
  744. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  745. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  746. devicetype = DeviceType.objects.create(
  747. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  748. )
  749. role = DeviceRole.objects.create(
  750. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  751. )
  752. device1 = Device.objects.create(
  753. device_type=devicetype, role=role, name='TestDevice1', site=site
  754. )
  755. device2 = Device.objects.create(
  756. device_type=devicetype, role=role, name='TestDevice2', site=site
  757. )
  758. interfaces = (
  759. Interface(device=device1, name='eth0'),
  760. Interface(device=device2, name='eth0'),
  761. Interface(device=device2, name='eth1'),
  762. )
  763. Interface.objects.bulk_create(interfaces)
  764. Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[1]]).save()
  765. PowerPort.objects.create(device=device2, name='psu1')
  766. patch_panel = Device.objects.create(
  767. device_type=devicetype, role=role, name='TestPatchPanel', site=site
  768. )
  769. rear_ports = (
  770. RearPort(device=patch_panel, name='RP1', type='8p8c'),
  771. RearPort(device=patch_panel, name='RP2', type='8p8c', positions=2),
  772. RearPort(device=patch_panel, name='RP3', type='8p8c', positions=3),
  773. RearPort(device=patch_panel, name='RP4', type='8p8c', positions=3),
  774. )
  775. RearPort.objects.bulk_create(rear_ports)
  776. front_ports = (
  777. FrontPort(device=patch_panel, name='FP1', type='8p8c', rear_port=rear_ports[0], rear_port_position=1),
  778. FrontPort(device=patch_panel, name='FP2', type='8p8c', rear_port=rear_ports[1], rear_port_position=1),
  779. FrontPort(device=patch_panel, name='FP3', type='8p8c', rear_port=rear_ports[2], rear_port_position=1),
  780. FrontPort(device=patch_panel, name='FP4', type='8p8c', rear_port=rear_ports[3], rear_port_position=1),
  781. )
  782. FrontPort.objects.bulk_create(front_ports)
  783. provider = Provider.objects.create(name='Provider 1', slug='provider-1')
  784. provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider)
  785. circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
  786. circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1')
  787. circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2')
  788. CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='A')
  789. CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='Z')
  790. CircuitTermination.objects.create(circuit=circuit2, termination=provider_network, term_side='A')
  791. def test_cable_creation(self):
  792. """
  793. When a new Cable is created, it must be cached on either termination point.
  794. """
  795. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  796. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  797. cable = Cable.objects.first()
  798. self.assertEqual(interface1.cable, cable)
  799. self.assertEqual(interface2.cable, cable)
  800. self.assertEqual(interface1.cable_end, 'A')
  801. self.assertEqual(interface2.cable_end, 'B')
  802. self.assertEqual(interface1.link_peers, [interface2])
  803. self.assertEqual(interface2.link_peers, [interface1])
  804. def test_cable_deletion(self):
  805. """
  806. When a Cable is deleted, the `cable` field on its termination points must be nullified. The str() method
  807. should still return the PK of the string even after being nullified.
  808. """
  809. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  810. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  811. cable = Cable.objects.first()
  812. cable.delete()
  813. self.assertIsNone(cable.pk)
  814. self.assertNotEqual(str(cable), '#None')
  815. interface1 = Interface.objects.get(pk=interface1.pk)
  816. self.assertIsNone(interface1.cable)
  817. self.assertListEqual(interface1.link_peers, [])
  818. interface2 = Interface.objects.get(pk=interface2.pk)
  819. self.assertIsNone(interface2.cable)
  820. self.assertListEqual(interface2.link_peers, [])
  821. def test_cable_validates_same_parent_object(self):
  822. """
  823. The clean method should ensure that all terminations at either end of a Cable belong to the same parent object.
  824. """
  825. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  826. powerport1 = PowerPort.objects.get(device__name='TestDevice2', name='psu1')
  827. cable = Cable(a_terminations=[interface1], b_terminations=[powerport1])
  828. with self.assertRaises(ValidationError):
  829. cable.clean()
  830. def test_cable_validates_same_type(self):
  831. """
  832. The clean method should ensure that all terminations at either end of a Cable are of the same type.
  833. """
  834. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  835. frontport1 = FrontPort.objects.get(device__name='TestPatchPanel', name='FP1')
  836. rearport1 = RearPort.objects.get(device__name='TestPatchPanel', name='RP1')
  837. cable = Cable(a_terminations=[frontport1, rearport1], b_terminations=[interface1])
  838. with self.assertRaises(ValidationError):
  839. cable.clean()
  840. def test_cable_validates_compatible_types(self):
  841. """
  842. The clean method should have a check to ensure only compatible port types can be connected by a cable
  843. """
  844. interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
  845. powerport1 = PowerPort.objects.get(device__name='TestDevice2', name='psu1')
  846. # An interface cannot be connected to a power port, for example
  847. cable = Cable(a_terminations=[interface1], b_terminations=[powerport1])
  848. with self.assertRaises(ValidationError):
  849. cable.clean()
  850. def test_cable_cannot_terminate_to_a_provider_network_circuittermination(self):
  851. """
  852. Neither side of a cable can be terminated to a CircuitTermination which is attached to a ProviderNetwork
  853. """
  854. interface3 = Interface.objects.get(device__name='TestDevice2', name='eth1')
  855. circuittermination3 = CircuitTermination.objects.get(circuit__cid='2', term_side='A')
  856. cable = Cable(a_terminations=[interface3], b_terminations=[circuittermination3])
  857. with self.assertRaises(ValidationError):
  858. cable.clean()
  859. def test_cable_cannot_terminate_to_a_virtual_interface(self):
  860. """
  861. A cable cannot terminate to a virtual interface
  862. """
  863. device1 = Device.objects.get(name='TestDevice1')
  864. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  865. virtual_interface = Interface(device=device1, name="V1", type=InterfaceTypeChoices.TYPE_VIRTUAL)
  866. cable = Cable(a_terminations=[interface2], b_terminations=[virtual_interface])
  867. with self.assertRaises(ValidationError):
  868. cable.clean()
  869. def test_cable_cannot_terminate_to_a_wireless_interface(self):
  870. """
  871. A cable cannot terminate to a wireless interface
  872. """
  873. device1 = Device.objects.get(name='TestDevice1')
  874. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  875. wireless_interface = Interface(device=device1, name="W1", type=InterfaceTypeChoices.TYPE_80211A)
  876. cable = Cable(a_terminations=[interface2], b_terminations=[wireless_interface])
  877. with self.assertRaises(ValidationError):
  878. cable.clean()
  879. @tag('regression')
  880. def test_cable_cannot_terminate_to_a_cellular_interface(self):
  881. """
  882. A cable cannot terminate to a cellular interface
  883. """
  884. device1 = Device.objects.get(name='TestDevice1')
  885. interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
  886. cellular_interface = Interface(device=device1, name="W1", type=InterfaceTypeChoices.TYPE_LTE)
  887. cable = Cable(a_terminations=[interface2], b_terminations=[cellular_interface])
  888. with self.assertRaises(ValidationError):
  889. cable.clean()
  890. def test_cannot_cable_to_mark_connected(self):
  891. """
  892. Test that a cable cannot be connected to an interface marked as connected.
  893. """
  894. device1 = Device.objects.get(name='TestDevice1')
  895. interface1 = Interface.objects.get(device__name='TestDevice2', name='eth1')
  896. mark_connected_interface = Interface(device=device1, name='mark_connected1', mark_connected=True)
  897. cable = Cable(a_terminations=[mark_connected_interface], b_terminations=[interface1])
  898. with self.assertRaises(ValidationError):
  899. cable.clean()
  900. class VirtualDeviceContextTestCase(TestCase):
  901. @classmethod
  902. def setUpTestData(cls):
  903. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  904. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  905. devicetype = DeviceType.objects.create(
  906. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  907. )
  908. role = DeviceRole.objects.create(
  909. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  910. )
  911. Device.objects.create(
  912. device_type=devicetype, role=role, name='TestDevice1', site=site
  913. )
  914. def test_vdc_and_interface_creation(self):
  915. device = Device.objects.first()
  916. vdc = VirtualDeviceContext(device=device, name="VDC 1", identifier=1, status='active')
  917. vdc.full_clean()
  918. vdc.save()
  919. interface = Interface(device=device, name='Eth1/1', type='10gbase-t')
  920. interface.full_clean()
  921. interface.save()
  922. interface.vdcs.set([vdc])
  923. def test_vdc_duplicate_name(self):
  924. device = Device.objects.first()
  925. vdc1 = VirtualDeviceContext(device=device, name="VDC 1", identifier=1, status='active')
  926. vdc1.full_clean()
  927. vdc1.save()
  928. vdc2 = VirtualDeviceContext(device=device, name="VDC 1", identifier=2, status='active')
  929. with self.assertRaises(ValidationError):
  930. vdc2.full_clean()
  931. def test_vdc_duplicate_identifier(self):
  932. device = Device.objects.first()
  933. vdc1 = VirtualDeviceContext(device=device, name="VDC 1", identifier=1, status='active')
  934. vdc1.full_clean()
  935. vdc1.save()
  936. vdc2 = VirtualDeviceContext(device=device, name="VDC 2", identifier=1, status='active')
  937. with self.assertRaises(ValidationError):
  938. vdc2.full_clean()
  939. class VirtualChassisTestCase(TestCase):
  940. @classmethod
  941. def setUpTestData(cls):
  942. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  943. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  944. devicetype = DeviceType.objects.create(
  945. manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
  946. )
  947. role = DeviceRole.objects.create(
  948. name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
  949. )
  950. Device.objects.create(
  951. device_type=devicetype, role=role, name='TestDevice1', site=site
  952. )
  953. Device.objects.create(
  954. device_type=devicetype, role=role, name='TestDevice2', site=site
  955. )
  956. def test_virtualchassis_deletion_clears_vc_position(self):
  957. """
  958. Test that when a VirtualChassis is deleted, member devices have their
  959. vc_position and vc_priority fields set to None.
  960. """
  961. devices = Device.objects.all()
  962. device1 = devices[0]
  963. device2 = devices[1]
  964. # Create a VirtualChassis with two member devices
  965. vc = VirtualChassis.objects.create(name='Test VC', master=device1)
  966. device1.virtual_chassis = vc
  967. device1.vc_position = 1
  968. device1.vc_priority = 10
  969. device1.save()
  970. device2.virtual_chassis = vc
  971. device2.vc_position = 2
  972. device2.vc_priority = 20
  973. device2.save()
  974. # Verify devices are members of the VC with positions set
  975. device1.refresh_from_db()
  976. device2.refresh_from_db()
  977. self.assertEqual(device1.virtual_chassis, vc)
  978. self.assertEqual(device1.vc_position, 1)
  979. self.assertEqual(device1.vc_priority, 10)
  980. self.assertEqual(device2.virtual_chassis, vc)
  981. self.assertEqual(device2.vc_position, 2)
  982. self.assertEqual(device2.vc_priority, 20)
  983. # Delete the VirtualChassis
  984. vc.delete()
  985. # Verify devices have vc_position and vc_priority set to None
  986. device1.refresh_from_db()
  987. device2.refresh_from_db()
  988. self.assertIsNone(device1.virtual_chassis)
  989. self.assertIsNone(device1.vc_position)
  990. self.assertIsNone(device1.vc_priority)
  991. self.assertIsNone(device2.virtual_chassis)
  992. self.assertIsNone(device2.vc_position)
  993. self.assertIsNone(device2.vc_priority)
  994. def test_virtualchassis_duplicate_vc_position(self):
  995. """
  996. Test that two devices cannot be assigned to the same vc_position
  997. within the same VirtualChassis.
  998. """
  999. devices = Device.objects.all()
  1000. device1 = devices[0]
  1001. device2 = devices[1]
  1002. # Create a VirtualChassis
  1003. vc = VirtualChassis.objects.create(name='Test VC')
  1004. # Assign first device to vc_position 1
  1005. device1.virtual_chassis = vc
  1006. device1.vc_position = 1
  1007. device1.full_clean()
  1008. device1.save()
  1009. # Try to assign second device to the same vc_position
  1010. device2.virtual_chassis = vc
  1011. device2.vc_position = 1
  1012. with self.assertRaises(ValidationError):
  1013. device2.full_clean()
  1014. class SiteSignalTestCase(TestCase):
  1015. @tag('regression')
  1016. def test_edit_site_with_prefix_no_vrf(self):
  1017. site = Site.objects.create(name='Test Site', slug='test-site')
  1018. Prefix.objects.create(prefix='192.0.2.0/24', scope=site, vrf=None)
  1019. # Regression test for #21045: should not raise ValueError
  1020. site.save()