test_cablepaths2.py 110 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852
  1. from unittest import skip
  2. from circuits.models import Circuit, CircuitTermination, ProviderNetwork
  3. from dcim.choices import CableLengthUnitChoices, CableProfileChoices
  4. from dcim.models import *
  5. from dcim.svg import CableTraceSVG
  6. from dcim.tests.utils import BaseCablePathTestCase
  7. from netbox.choices import DistanceUnitChoices
  8. class CablePathTestCase(BaseCablePathTestCase):
  9. """
  10. Test the creation of CablePaths for Cables with different profiles applied.
  11. Tests are numbered as follows:
  12. 1XX: Test direct connections using each profile
  13. 2XX: Topology tests replicated from the legacy test case and adapted to use profiles
  14. 3XX: Dynamic port mapping, profile and termination changes
  15. """
  16. def test_101_cable_profile_single_1c1p(self):
  17. """
  18. [IF1] --C1-- [IF2]
  19. Cable profile: Single connector, single position
  20. """
  21. interfaces = [
  22. Interface.objects.create(device=self.device, name='Interface 1'),
  23. Interface.objects.create(device=self.device, name='Interface 2'),
  24. ]
  25. # Create cable 1
  26. cable1 = Cable(
  27. profile=CableProfileChoices.SINGLE_1C1P,
  28. a_terminations=[interfaces[0]],
  29. b_terminations=[interfaces[1]],
  30. )
  31. cable1.clean()
  32. cable1.save()
  33. path1 = self.assertPathExists(
  34. (interfaces[0], cable1, interfaces[1]),
  35. is_complete=True,
  36. is_active=True
  37. )
  38. path2 = self.assertPathExists(
  39. (interfaces[1], cable1, interfaces[0]),
  40. is_complete=True,
  41. is_active=True
  42. )
  43. self.assertEqual(CablePath.objects.count(), 2)
  44. interfaces[0].refresh_from_db()
  45. interfaces[1].refresh_from_db()
  46. self.assertPathIsSet(interfaces[0], path1)
  47. self.assertPathIsSet(interfaces[1], path2)
  48. self.assertEqual(interfaces[0].cable_connector, 1)
  49. self.assertEqual(interfaces[0].cable_positions, [1])
  50. self.assertEqual(interfaces[1].cable_connector, 1)
  51. self.assertEqual(interfaces[1].cable_positions, [1])
  52. # Test SVG generation
  53. CableTraceSVG(interfaces[0]).render()
  54. # Delete cable 1
  55. cable1.delete()
  56. # Check that all CablePaths have been deleted
  57. self.assertEqual(CablePath.objects.count(), 0)
  58. def test_102_cable_profile_single_1c2p(self):
  59. """
  60. [IF1] --C1-- [FP1][RP1] --C3-- [RP2][FP3] --C4-- [IF3]
  61. [IF2] --C2-- [FP2] [FP4] --C5-- [IF4]
  62. Cable profile: Single connector, multiple positions
  63. """
  64. interfaces = [
  65. Interface.objects.create(device=self.device, name='Interface 1'),
  66. Interface.objects.create(device=self.device, name='Interface 2'),
  67. Interface.objects.create(device=self.device, name='Interface 3'),
  68. Interface.objects.create(device=self.device, name='Interface 4'),
  69. ]
  70. rear_ports = [
  71. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
  72. RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
  73. ]
  74. front_ports = [
  75. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  76. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  77. FrontPort.objects.create(device=self.device, name='Front Port 3'),
  78. FrontPort.objects.create(device=self.device, name='Front Port 4'),
  79. ]
  80. PortMapping.objects.bulk_create([
  81. PortMapping(
  82. device=self.device,
  83. front_port=front_ports[0],
  84. front_port_position=1,
  85. rear_port=rear_ports[0],
  86. rear_port_position=1,
  87. ),
  88. PortMapping(
  89. device=self.device,
  90. front_port=front_ports[1],
  91. front_port_position=1,
  92. rear_port=rear_ports[0],
  93. rear_port_position=2,
  94. ),
  95. PortMapping(
  96. device=self.device,
  97. front_port=front_ports[2],
  98. front_port_position=1,
  99. rear_port=rear_ports[1],
  100. rear_port_position=1,
  101. ),
  102. PortMapping(
  103. device=self.device,
  104. front_port=front_ports[3],
  105. front_port_position=1,
  106. rear_port=rear_ports[1],
  107. rear_port_position=2,
  108. ),
  109. ])
  110. # Create cables
  111. cable1 = Cable(
  112. a_terminations=[interfaces[0]],
  113. b_terminations=[front_ports[0]],
  114. )
  115. cable1.clean()
  116. cable1.save()
  117. cable2 = Cable(
  118. a_terminations=[interfaces[1]],
  119. b_terminations=[front_ports[1]],
  120. )
  121. cable2.clean()
  122. cable2.save()
  123. cable3 = Cable(
  124. profile=CableProfileChoices.SINGLE_1C2P,
  125. a_terminations=[rear_ports[0]],
  126. b_terminations=[rear_ports[1]],
  127. )
  128. cable3.clean()
  129. cable3.save()
  130. cable4 = Cable(
  131. a_terminations=[interfaces[2]],
  132. b_terminations=[front_ports[2]],
  133. )
  134. cable4.clean()
  135. cable4.save()
  136. cable5 = Cable(
  137. a_terminations=[interfaces[3]],
  138. b_terminations=[front_ports[3]],
  139. )
  140. cable5.clean()
  141. cable5.save()
  142. path1 = self.assertPathExists(
  143. (
  144. interfaces[0], cable1, front_ports[0], rear_ports[0], cable3, rear_ports[1], front_ports[2], cable4,
  145. interfaces[2],
  146. ),
  147. is_complete=True,
  148. is_active=True
  149. )
  150. path2 = self.assertPathExists(
  151. (
  152. interfaces[1], cable2, front_ports[1], rear_ports[0], cable3, rear_ports[1], front_ports[3], cable5,
  153. interfaces[3],
  154. ),
  155. is_complete=True,
  156. is_active=True
  157. )
  158. path3 = self.assertPathExists(
  159. (
  160. interfaces[2], cable4, front_ports[2], rear_ports[1], cable3, rear_ports[0], front_ports[0], cable1,
  161. interfaces[0],
  162. ),
  163. is_complete=True,
  164. is_active=True
  165. )
  166. path4 = self.assertPathExists(
  167. (
  168. interfaces[3], cable5, front_ports[3], rear_ports[1], cable3, rear_ports[0], front_ports[1], cable2,
  169. interfaces[1],
  170. ),
  171. is_complete=True,
  172. is_active=True
  173. )
  174. self.assertEqual(CablePath.objects.count(), 4)
  175. for iface in interfaces:
  176. iface.refresh_from_db()
  177. self.assertPathIsSet(interfaces[0], path1)
  178. self.assertPathIsSet(interfaces[1], path2)
  179. self.assertPathIsSet(interfaces[2], path3)
  180. self.assertPathIsSet(interfaces[3], path4)
  181. for rear_port in rear_ports:
  182. rear_port.refresh_from_db()
  183. self.assertEqual(rear_ports[0].cable_connector, 1)
  184. self.assertEqual(rear_ports[0].cable_positions, [1, 2])
  185. self.assertEqual(rear_ports[1].cable_connector, 1)
  186. self.assertEqual(rear_ports[1].cable_positions, [1, 2])
  187. # Test SVG generation
  188. CableTraceSVG(interfaces[0]).render()
  189. def test_103_cable_profile_trunk_2c1p(self):
  190. """
  191. [IF1] --C1-- [IF3]
  192. [IF2] [IF4]
  193. Cable profile: Multiple connectors, single position
  194. """
  195. interfaces = [
  196. Interface.objects.create(device=self.device, name='Interface 1'),
  197. Interface.objects.create(device=self.device, name='Interface 2'),
  198. Interface.objects.create(device=self.device, name='Interface 3'),
  199. Interface.objects.create(device=self.device, name='Interface 4'),
  200. ]
  201. # Create cable 1
  202. cable1 = Cable(
  203. profile=CableProfileChoices.TRUNK_2C1P,
  204. a_terminations=[interfaces[0], interfaces[1]],
  205. b_terminations=[interfaces[2], interfaces[3]],
  206. )
  207. cable1.clean()
  208. cable1.save()
  209. path1 = self.assertPathExists(
  210. (interfaces[0], cable1, interfaces[2]),
  211. is_complete=True,
  212. is_active=True
  213. )
  214. path2 = self.assertPathExists(
  215. (interfaces[1], cable1, interfaces[3]),
  216. is_complete=True,
  217. is_active=True
  218. )
  219. path3 = self.assertPathExists(
  220. (interfaces[2], cable1, interfaces[0]),
  221. is_complete=True,
  222. is_active=True
  223. )
  224. path4 = self.assertPathExists(
  225. (interfaces[3], cable1, interfaces[1]),
  226. is_complete=True,
  227. is_active=True
  228. )
  229. self.assertEqual(CablePath.objects.count(), 4)
  230. for interface in interfaces:
  231. interface.refresh_from_db()
  232. self.assertPathIsSet(interfaces[0], path1)
  233. self.assertPathIsSet(interfaces[1], path2)
  234. self.assertPathIsSet(interfaces[2], path3)
  235. self.assertPathIsSet(interfaces[3], path4)
  236. self.assertEqual(interfaces[0].cable_connector, 1)
  237. self.assertEqual(interfaces[0].cable_positions, [1])
  238. self.assertEqual(interfaces[1].cable_connector, 2)
  239. self.assertEqual(interfaces[1].cable_positions, [1])
  240. self.assertEqual(interfaces[2].cable_connector, 1)
  241. self.assertEqual(interfaces[2].cable_positions, [1])
  242. self.assertEqual(interfaces[3].cable_connector, 2)
  243. self.assertEqual(interfaces[3].cable_positions, [1])
  244. # Test SVG generation
  245. CableTraceSVG(interfaces[0]).render()
  246. # Delete cable 1
  247. cable1.delete()
  248. # Check that all CablePaths have been deleted
  249. self.assertEqual(CablePath.objects.count(), 0)
  250. def test_104_cable_profile_trunk_2c2p(self):
  251. """
  252. [IF1] --C1-- [FP1][RP1] --C9-- [RP3][FP5] --C5-- [IF5]
  253. [IF2] --C2-- [FP2] [FP6] --C6-- [IF6]
  254. [IF3] --C3-- [FP3][RP2] [RP4][FP7] --C7-- [IF7]
  255. [IF4] --C4-- [FP4] [FP8] --C8-- [IF8]
  256. Cable profile: Multiple connectors, multiple positions
  257. """
  258. interfaces = [
  259. Interface.objects.create(device=self.device, name='Interface 1'),
  260. Interface.objects.create(device=self.device, name='Interface 2'),
  261. Interface.objects.create(device=self.device, name='Interface 3'),
  262. Interface.objects.create(device=self.device, name='Interface 4'),
  263. Interface.objects.create(device=self.device, name='Interface 5'),
  264. Interface.objects.create(device=self.device, name='Interface 6'),
  265. Interface.objects.create(device=self.device, name='Interface 7'),
  266. Interface.objects.create(device=self.device, name='Interface 8'),
  267. ]
  268. rear_ports = [
  269. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
  270. RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
  271. RearPort.objects.create(device=self.device, name='Rear Port 3', positions=2),
  272. RearPort.objects.create(device=self.device, name='Rear Port 4', positions=2),
  273. ]
  274. front_ports = [
  275. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  276. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  277. FrontPort.objects.create(device=self.device, name='Front Port 3'),
  278. FrontPort.objects.create(device=self.device, name='Front Port 4'),
  279. FrontPort.objects.create(device=self.device, name='Front Port 5'),
  280. FrontPort.objects.create(device=self.device, name='Front Port 6'),
  281. FrontPort.objects.create(device=self.device, name='Front Port 7'),
  282. FrontPort.objects.create(device=self.device, name='Front Port 8'),
  283. ]
  284. PortMapping.objects.bulk_create([
  285. PortMapping(
  286. device=self.device,
  287. front_port=front_ports[0],
  288. front_port_position=1,
  289. rear_port=rear_ports[0],
  290. rear_port_position=1,
  291. ),
  292. PortMapping(
  293. device=self.device,
  294. front_port=front_ports[1],
  295. front_port_position=1,
  296. rear_port=rear_ports[0],
  297. rear_port_position=2,
  298. ),
  299. PortMapping(
  300. device=self.device,
  301. front_port=front_ports[2],
  302. front_port_position=1,
  303. rear_port=rear_ports[1],
  304. rear_port_position=1,
  305. ),
  306. PortMapping(
  307. device=self.device,
  308. front_port=front_ports[3],
  309. front_port_position=1,
  310. rear_port=rear_ports[1],
  311. rear_port_position=2,
  312. ),
  313. PortMapping(
  314. device=self.device,
  315. front_port=front_ports[4],
  316. front_port_position=1,
  317. rear_port=rear_ports[2],
  318. rear_port_position=1,
  319. ),
  320. PortMapping(
  321. device=self.device,
  322. front_port=front_ports[5],
  323. front_port_position=1,
  324. rear_port=rear_ports[2],
  325. rear_port_position=2,
  326. ),
  327. PortMapping(
  328. device=self.device,
  329. front_port=front_ports[6],
  330. front_port_position=1,
  331. rear_port=rear_ports[3],
  332. rear_port_position=1,
  333. ),
  334. PortMapping(
  335. device=self.device,
  336. front_port=front_ports[7],
  337. front_port_position=1,
  338. rear_port=rear_ports[3],
  339. rear_port_position=2,
  340. ),
  341. ])
  342. # Create cables
  343. cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[front_ports[0]])
  344. cable1.clean()
  345. cable1.save()
  346. cable2 = Cable(a_terminations=[interfaces[1]], b_terminations=[front_ports[1]])
  347. cable2.clean()
  348. cable2.save()
  349. cable3 = Cable(a_terminations=[interfaces[2]], b_terminations=[front_ports[2]])
  350. cable3.clean()
  351. cable3.save()
  352. cable4 = Cable(a_terminations=[interfaces[3]], b_terminations=[front_ports[3]])
  353. cable4.clean()
  354. cable4.save()
  355. cable5 = Cable(a_terminations=[interfaces[4]], b_terminations=[front_ports[4]])
  356. cable5.clean()
  357. cable5.save()
  358. cable6 = Cable(a_terminations=[interfaces[5]], b_terminations=[front_ports[5]])
  359. cable6.clean()
  360. cable6.save()
  361. cable7 = Cable(a_terminations=[interfaces[6]], b_terminations=[front_ports[6]])
  362. cable7.clean()
  363. cable7.save()
  364. cable8 = Cable(a_terminations=[interfaces[7]], b_terminations=[front_ports[7]])
  365. cable8.clean()
  366. cable8.save()
  367. cable9 = Cable(
  368. profile=CableProfileChoices.TRUNK_2C2P,
  369. a_terminations=[rear_ports[0], rear_ports[1]],
  370. b_terminations=[rear_ports[2], rear_ports[3]]
  371. )
  372. cable9.clean()
  373. cable9.save()
  374. path1 = self.assertPathExists(
  375. (
  376. interfaces[0], cable1, front_ports[0], rear_ports[0], cable9, rear_ports[2], front_ports[4], cable5,
  377. interfaces[4],
  378. ),
  379. is_complete=True,
  380. is_active=True
  381. )
  382. path2 = self.assertPathExists(
  383. (
  384. interfaces[1], cable2, front_ports[1], rear_ports[0], cable9, rear_ports[2], front_ports[5], cable6,
  385. interfaces[5],
  386. ),
  387. is_complete=True,
  388. is_active=True
  389. )
  390. path3 = self.assertPathExists(
  391. (
  392. interfaces[2], cable3, front_ports[2], rear_ports[1], cable9, rear_ports[3], front_ports[6], cable7,
  393. interfaces[6],
  394. ),
  395. is_complete=True,
  396. is_active=True
  397. )
  398. path4 = self.assertPathExists(
  399. (
  400. interfaces[3], cable4, front_ports[3], rear_ports[1], cable9, rear_ports[3], front_ports[7], cable8,
  401. interfaces[7],
  402. ),
  403. is_complete=True,
  404. is_active=True
  405. )
  406. path5 = self.assertPathExists(
  407. (
  408. interfaces[4], cable5, front_ports[4], rear_ports[2], cable9, rear_ports[0], front_ports[0], cable1,
  409. interfaces[0],
  410. ),
  411. is_complete=True,
  412. is_active=True
  413. )
  414. path6 = self.assertPathExists(
  415. (
  416. interfaces[5], cable6, front_ports[5], rear_ports[2], cable9, rear_ports[0], front_ports[1], cable2,
  417. interfaces[1],
  418. ),
  419. is_complete=True,
  420. is_active=True
  421. )
  422. path7 = self.assertPathExists(
  423. (
  424. interfaces[6], cable7, front_ports[6], rear_ports[3], cable9, rear_ports[1], front_ports[2], cable3,
  425. interfaces[2],
  426. ),
  427. is_complete=True,
  428. is_active=True
  429. )
  430. path8 = self.assertPathExists(
  431. (
  432. interfaces[7], cable8, front_ports[7], rear_ports[3], cable9, rear_ports[1], front_ports[3], cable4,
  433. interfaces[3],
  434. ),
  435. is_complete=True,
  436. is_active=True
  437. )
  438. self.assertEqual(CablePath.objects.count(), 8)
  439. for iface in interfaces:
  440. iface.refresh_from_db()
  441. self.assertPathIsSet(interfaces[0], path1)
  442. self.assertPathIsSet(interfaces[1], path2)
  443. self.assertPathIsSet(interfaces[2], path3)
  444. self.assertPathIsSet(interfaces[3], path4)
  445. self.assertPathIsSet(interfaces[4], path5)
  446. self.assertPathIsSet(interfaces[5], path6)
  447. self.assertPathIsSet(interfaces[6], path7)
  448. self.assertPathIsSet(interfaces[7], path8)
  449. for rear_port in rear_ports:
  450. rear_port.refresh_from_db()
  451. self.assertEqual(rear_ports[0].cable_connector, 1)
  452. self.assertEqual(rear_ports[0].cable_positions, [1, 2])
  453. self.assertEqual(rear_ports[1].cable_connector, 2)
  454. self.assertEqual(rear_ports[1].cable_positions, [1, 2])
  455. self.assertEqual(rear_ports[2].cable_connector, 1)
  456. self.assertEqual(rear_ports[2].cable_positions, [1, 2])
  457. self.assertEqual(rear_ports[3].cable_connector, 2)
  458. self.assertEqual(rear_ports[3].cable_positions, [1, 2])
  459. # Test SVG generation
  460. CableTraceSVG(interfaces[0]).render()
  461. def test_105_cable_profile_breakout(self):
  462. """
  463. [IF1] --C1-- [FP1][RP1] --C2-- [IF5]
  464. [IF2] --C3-- [FP2] [IF6]
  465. [IF3] --C4-- [FP3] [IF7]
  466. [IF4] --C5-- [FP4] [IF8]
  467. Cable profile: 1:4 breakout
  468. """
  469. interfaces = [
  470. Interface.objects.create(device=self.device, name='Interface 1'),
  471. Interface.objects.create(device=self.device, name='Interface 2'),
  472. Interface.objects.create(device=self.device, name='Interface 3'),
  473. Interface.objects.create(device=self.device, name='Interface 4'),
  474. Interface.objects.create(device=self.device, name='Interface 5'),
  475. Interface.objects.create(device=self.device, name='Interface 6'),
  476. Interface.objects.create(device=self.device, name='Interface 7'),
  477. Interface.objects.create(device=self.device, name='Interface 8'),
  478. ]
  479. rear_ports = [
  480. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4),
  481. ]
  482. front_ports = [
  483. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  484. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  485. FrontPort.objects.create(device=self.device, name='Front Port 3'),
  486. FrontPort.objects.create(device=self.device, name='Front Port 4'),
  487. ]
  488. PortMapping.objects.bulk_create([
  489. PortMapping(
  490. device=self.device,
  491. front_port=front_ports[0],
  492. front_port_position=1,
  493. rear_port=rear_ports[0],
  494. rear_port_position=1,
  495. ),
  496. PortMapping(
  497. device=self.device,
  498. front_port=front_ports[1],
  499. front_port_position=1,
  500. rear_port=rear_ports[0],
  501. rear_port_position=2,
  502. ),
  503. PortMapping(
  504. device=self.device,
  505. front_port=front_ports[2],
  506. front_port_position=1,
  507. rear_port=rear_ports[0],
  508. rear_port_position=3,
  509. ),
  510. PortMapping(
  511. device=self.device,
  512. front_port=front_ports[3],
  513. front_port_position=1,
  514. rear_port=rear_ports[0],
  515. rear_port_position=4,
  516. ),
  517. ])
  518. # Create cables
  519. cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[front_ports[0]])
  520. cable1.clean()
  521. cable1.save()
  522. cable2 = Cable(a_terminations=[interfaces[1]], b_terminations=[front_ports[1]])
  523. cable2.clean()
  524. cable2.save()
  525. cable3 = Cable(a_terminations=[interfaces[2]], b_terminations=[front_ports[2]])
  526. cable3.clean()
  527. cable3.save()
  528. cable4 = Cable(a_terminations=[interfaces[3]], b_terminations=[front_ports[3]])
  529. cable4.clean()
  530. cable4.save()
  531. cable5 = Cable(
  532. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  533. a_terminations=[rear_ports[0]],
  534. b_terminations=interfaces[4:8],
  535. )
  536. cable5.clean()
  537. cable5.save()
  538. path1 = self.assertPathExists(
  539. (interfaces[0], cable1, front_ports[0], rear_ports[0], cable5, interfaces[4]),
  540. is_complete=True,
  541. is_active=True
  542. )
  543. path2 = self.assertPathExists(
  544. (interfaces[1], cable2, front_ports[1], rear_ports[0], cable5, interfaces[5]),
  545. is_complete=True,
  546. is_active=True
  547. )
  548. path3 = self.assertPathExists(
  549. (interfaces[2], cable3, front_ports[2], rear_ports[0], cable5, interfaces[6]),
  550. is_complete=True,
  551. is_active=True
  552. )
  553. path4 = self.assertPathExists(
  554. (interfaces[3], cable4, front_ports[3], rear_ports[0], cable5, interfaces[7]),
  555. is_complete=True,
  556. is_active=True
  557. )
  558. path5 = self.assertPathExists(
  559. (interfaces[4], cable5, rear_ports[0], front_ports[0], cable1, interfaces[0]),
  560. is_complete=True,
  561. is_active=True
  562. )
  563. path6 = self.assertPathExists(
  564. (interfaces[5], cable5, rear_ports[0], front_ports[1], cable2, interfaces[1]),
  565. is_complete=True,
  566. is_active=True
  567. )
  568. path7 = self.assertPathExists(
  569. (interfaces[6], cable5, rear_ports[0], front_ports[2], cable3, interfaces[2]),
  570. is_complete=True,
  571. is_active=True
  572. )
  573. path8 = self.assertPathExists(
  574. (interfaces[7], cable5, rear_ports[0], front_ports[3], cable4, interfaces[3]),
  575. is_complete=True,
  576. is_active=True
  577. )
  578. self.assertEqual(CablePath.objects.count(), 8)
  579. for interface in interfaces:
  580. interface.refresh_from_db()
  581. self.assertPathIsSet(interfaces[0], path1)
  582. self.assertPathIsSet(interfaces[1], path2)
  583. self.assertPathIsSet(interfaces[2], path3)
  584. self.assertPathIsSet(interfaces[3], path4)
  585. self.assertPathIsSet(interfaces[4], path5)
  586. self.assertPathIsSet(interfaces[5], path6)
  587. self.assertPathIsSet(interfaces[6], path7)
  588. self.assertPathIsSet(interfaces[7], path8)
  589. self.assertEqual(interfaces[4].cable_connector, 1)
  590. self.assertEqual(interfaces[4].cable_positions, [1])
  591. self.assertEqual(interfaces[5].cable_connector, 2)
  592. self.assertEqual(interfaces[5].cable_positions, [1])
  593. self.assertEqual(interfaces[6].cable_connector, 3)
  594. self.assertEqual(interfaces[6].cable_positions, [1])
  595. self.assertEqual(interfaces[7].cable_connector, 4)
  596. self.assertEqual(interfaces[7].cable_positions, [1])
  597. rear_ports[0].refresh_from_db()
  598. self.assertEqual(rear_ports[0].cable_connector, 1)
  599. self.assertEqual(rear_ports[0].cable_positions, [1, 2, 3, 4])
  600. # Test SVG generation
  601. CableTraceSVG(interfaces[0]).render()
  602. def test_106_cable_profile_shuffle(self):
  603. """
  604. [IF1] --C1-- [FP1][RP1] --C17-- [RP3][FP9] --C9-- [IF9]
  605. [IF2] --C2-- [FP2] [FP10] --C10-- [IF10]
  606. [IF3] --C3-- [FP3] [FP11] --C11-- [IF11]
  607. [IF4] --C4-- [FP4] [FP12] --C12-- [IF12]
  608. [IF5] --C5-- [FP5][RP2] [RP4][FP13] --C13-- [IF9]
  609. [IF6] --C6-- [FP6] [FP14] --C14-- [IF10]
  610. [IF7] --C7-- [FP7] [FP15] --C15-- [IF11]
  611. [IF8] --C8-- [FP8] [FP16] --C16-- [IF12]
  612. Cable profile: Shuffle (2x2 MPO8)
  613. """
  614. interfaces = [
  615. # A side
  616. Interface.objects.create(device=self.device, name='Interface 1:1'),
  617. Interface.objects.create(device=self.device, name='Interface 1:2'),
  618. Interface.objects.create(device=self.device, name='Interface 1:3'),
  619. Interface.objects.create(device=self.device, name='Interface 1:4'),
  620. Interface.objects.create(device=self.device, name='Interface 2:1'),
  621. Interface.objects.create(device=self.device, name='Interface 2:2'),
  622. Interface.objects.create(device=self.device, name='Interface 2:3'),
  623. Interface.objects.create(device=self.device, name='Interface 2:4'),
  624. # B side
  625. Interface.objects.create(device=self.device, name='Interface 3:1'),
  626. Interface.objects.create(device=self.device, name='Interface 3:2'),
  627. Interface.objects.create(device=self.device, name='Interface 3:3'),
  628. Interface.objects.create(device=self.device, name='Interface 3:4'),
  629. Interface.objects.create(device=self.device, name='Interface 4:1'),
  630. Interface.objects.create(device=self.device, name='Interface 4:2'),
  631. Interface.objects.create(device=self.device, name='Interface 4:3'),
  632. Interface.objects.create(device=self.device, name='Interface 4:4'),
  633. ]
  634. rear_ports = [
  635. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4),
  636. RearPort.objects.create(device=self.device, name='Rear Port 2', positions=4),
  637. RearPort.objects.create(device=self.device, name='Rear Port 3', positions=4),
  638. RearPort.objects.create(device=self.device, name='Rear Port 4', positions=4),
  639. ]
  640. front_ports = [
  641. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  642. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  643. FrontPort.objects.create(device=self.device, name='Front Port 3'),
  644. FrontPort.objects.create(device=self.device, name='Front Port 4'),
  645. FrontPort.objects.create(device=self.device, name='Front Port 5'),
  646. FrontPort.objects.create(device=self.device, name='Front Port 6'),
  647. FrontPort.objects.create(device=self.device, name='Front Port 7'),
  648. FrontPort.objects.create(device=self.device, name='Front Port 8'),
  649. FrontPort.objects.create(device=self.device, name='Front Port 9'),
  650. FrontPort.objects.create(device=self.device, name='Front Port 10'),
  651. FrontPort.objects.create(device=self.device, name='Front Port 11'),
  652. FrontPort.objects.create(device=self.device, name='Front Port 12'),
  653. FrontPort.objects.create(device=self.device, name='Front Port 13'),
  654. FrontPort.objects.create(device=self.device, name='Front Port 14'),
  655. FrontPort.objects.create(device=self.device, name='Front Port 15'),
  656. FrontPort.objects.create(device=self.device, name='Front Port 16'),
  657. ]
  658. port_mappings = []
  659. for i, front_port in enumerate(front_ports):
  660. port_mappings.append(
  661. PortMapping(
  662. device=self.device,
  663. front_port=front_ports[i],
  664. front_port_position=1,
  665. rear_port=rear_ports[int(i / 4)],
  666. rear_port_position=(i % 4) + 1,
  667. ),
  668. )
  669. PortMapping.objects.bulk_create(port_mappings)
  670. # Create cables
  671. cables = []
  672. for interface, front_port in zip(interfaces, front_ports):
  673. cable = Cable(a_terminations=[interface], b_terminations=[front_port])
  674. cable.clean()
  675. cable.save()
  676. cables.append(cable)
  677. shuffle_cable = Cable(
  678. profile=CableProfileChoices.TRUNK_2C4P_SHUFFLE,
  679. a_terminations=rear_ports[0:2],
  680. b_terminations=rear_ports[2:4],
  681. )
  682. shuffle_cable.clean()
  683. shuffle_cable.save()
  684. paths = [
  685. # A-to-B paths
  686. self.assertPathExists(
  687. (
  688. interfaces[0], cables[0], front_ports[0], rear_ports[0], shuffle_cable, rear_ports[2],
  689. front_ports[8], cables[8], interfaces[8],
  690. ),
  691. is_complete=True,
  692. is_active=True
  693. ),
  694. self.assertPathExists(
  695. (
  696. interfaces[1], cables[1], front_ports[1], rear_ports[0], shuffle_cable, rear_ports[2],
  697. front_ports[9], cables[9], interfaces[9],
  698. ),
  699. is_complete=True,
  700. is_active=True
  701. ),
  702. self.assertPathExists(
  703. (
  704. interfaces[2], cables[2], front_ports[2], rear_ports[0], shuffle_cable, rear_ports[3],
  705. front_ports[12], cables[12], interfaces[12],
  706. ),
  707. is_complete=True,
  708. is_active=True
  709. ),
  710. self.assertPathExists(
  711. (
  712. interfaces[3], cables[3], front_ports[3], rear_ports[0], shuffle_cable, rear_ports[3],
  713. front_ports[13], cables[13], interfaces[13],
  714. ),
  715. is_complete=True,
  716. is_active=True
  717. ),
  718. self.assertPathExists(
  719. (
  720. interfaces[4], cables[4], front_ports[4], rear_ports[1], shuffle_cable, rear_ports[2],
  721. front_ports[10], cables[10], interfaces[10],
  722. ),
  723. is_complete=True,
  724. is_active=True
  725. ),
  726. self.assertPathExists(
  727. (
  728. interfaces[5], cables[5], front_ports[5], rear_ports[1], shuffle_cable, rear_ports[2],
  729. front_ports[11], cables[11], interfaces[11],
  730. ),
  731. is_complete=True,
  732. is_active=True
  733. ),
  734. self.assertPathExists(
  735. (
  736. interfaces[6], cables[6], front_ports[6], rear_ports[1], shuffle_cable, rear_ports[3],
  737. front_ports[14], cables[14], interfaces[14],
  738. ),
  739. is_complete=True,
  740. is_active=True
  741. ),
  742. self.assertPathExists(
  743. (
  744. interfaces[7], cables[7], front_ports[7], rear_ports[1], shuffle_cable, rear_ports[3],
  745. front_ports[15], cables[15], interfaces[15],
  746. ),
  747. is_complete=True,
  748. is_active=True
  749. ),
  750. ]
  751. self.assertEqual(CablePath.objects.count(), len(paths) * 2)
  752. for i, (interface, path) in enumerate(zip(interfaces, paths)):
  753. interface.refresh_from_db()
  754. self.assertPathIsSet(interface, path)
  755. for i, rear_port in enumerate(rear_ports):
  756. rear_port.refresh_from_db()
  757. self.assertEqual(rear_port.cable_connector, (i % 2) + 1)
  758. self.assertEqual(rear_port.cable_positions, [1, 2, 3, 4])
  759. # Test SVG generation
  760. CableTraceSVG(interfaces[0]).render()
  761. def test_107_duplex_interface_profiled_patch_through_trunk_with_splices(self):
  762. """
  763. Tests that a duplex interface (cable_positions=[1,2]) traces both positions through
  764. profiled cables and splice pass-throughs, producing a single CablePath with both
  765. strands visible.
  766. [IF1] -C1(1C2P)- [FP1(p=2)][RP1(p=2)] -C2(1C2P)- [RP2(p=2)]
  767. [FP2] -C3- [FP4][RP3(p=2)] -C4(1C2P)- [RP4(p=2)][FP6(p=2)]
  768. -C5(1C2P)- [IF2] / [FP3] -C6- [FP5]
  769. Cable profiles: C1=1C2P, C2=1C2P, C3/C6=unprofiled splices, C4=1C2P, C5=1C2P
  770. """
  771. interfaces = [
  772. Interface.objects.create(device=self.device, name='Interface 1'),
  773. Interface.objects.create(device=self.device, name='Interface 2'),
  774. ]
  775. rear_ports = [
  776. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
  777. RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
  778. RearPort.objects.create(device=self.device, name='Rear Port 3', positions=2),
  779. RearPort.objects.create(device=self.device, name='Rear Port 4', positions=2),
  780. ]
  781. front_ports = [
  782. FrontPort.objects.create(device=self.device, name='Front Port 1', positions=2), # Panel A duplex
  783. FrontPort.objects.create(device=self.device, name='Front Port 2'), # Splice A strand 1
  784. FrontPort.objects.create(device=self.device, name='Front Port 3'), # Splice A strand 2
  785. FrontPort.objects.create(device=self.device, name='Front Port 4'), # Splice B strand 1
  786. FrontPort.objects.create(device=self.device, name='Front Port 5'), # Splice B strand 2
  787. FrontPort.objects.create(device=self.device, name='Front Port 6', positions=2), # Panel B duplex
  788. ]
  789. PortMapping.objects.bulk_create([
  790. # Panel A: duplex FP1(pos=2) -> RP1(pos=2)
  791. PortMapping(
  792. device=self.device, front_port=front_ports[0], front_port_position=1,
  793. rear_port=rear_ports[0], rear_port_position=1,
  794. ),
  795. PortMapping(
  796. device=self.device, front_port=front_ports[0], front_port_position=2,
  797. rear_port=rear_ports[0], rear_port_position=2,
  798. ),
  799. # Splice A: FP2, FP3 -> RP2(pos=2)
  800. PortMapping(
  801. device=self.device, front_port=front_ports[1], front_port_position=1,
  802. rear_port=rear_ports[1], rear_port_position=1,
  803. ),
  804. PortMapping(
  805. device=self.device, front_port=front_ports[2], front_port_position=1,
  806. rear_port=rear_ports[1], rear_port_position=2,
  807. ),
  808. # Splice B: FP4, FP5 -> RP3(pos=2)
  809. PortMapping(
  810. device=self.device, front_port=front_ports[3], front_port_position=1,
  811. rear_port=rear_ports[2], rear_port_position=1,
  812. ),
  813. PortMapping(
  814. device=self.device, front_port=front_ports[4], front_port_position=1,
  815. rear_port=rear_ports[2], rear_port_position=2,
  816. ),
  817. # Panel B: duplex FP6(pos=2) -> RP4(pos=2)
  818. PortMapping(
  819. device=self.device, front_port=front_ports[5], front_port_position=1,
  820. rear_port=rear_ports[3], rear_port_position=1,
  821. ),
  822. PortMapping(
  823. device=self.device, front_port=front_ports[5], front_port_position=2,
  824. rear_port=rear_ports[3], rear_port_position=2,
  825. ),
  826. ])
  827. # Create cables
  828. cable1 = Cable(
  829. profile=CableProfileChoices.SINGLE_1C2P,
  830. a_terminations=[interfaces[0]],
  831. b_terminations=[front_ports[0]],
  832. )
  833. cable1.clean()
  834. cable1.save()
  835. cable2 = Cable(
  836. profile=CableProfileChoices.SINGLE_1C2P,
  837. a_terminations=[rear_ports[0]],
  838. b_terminations=[rear_ports[1]],
  839. )
  840. cable2.clean()
  841. cable2.save()
  842. cable3 = Cable(
  843. a_terminations=[front_ports[1]],
  844. b_terminations=[front_ports[3]],
  845. )
  846. cable3.clean()
  847. cable3.save()
  848. cable4 = Cable(
  849. profile=CableProfileChoices.SINGLE_1C2P,
  850. a_terminations=[rear_ports[2]],
  851. b_terminations=[rear_ports[3]],
  852. )
  853. cable4.clean()
  854. cable4.save()
  855. cable5 = Cable(
  856. profile=CableProfileChoices.SINGLE_1C2P,
  857. a_terminations=[front_ports[5]],
  858. b_terminations=[interfaces[1]],
  859. )
  860. cable5.clean()
  861. cable5.save()
  862. cable6 = Cable(
  863. a_terminations=[front_ports[2]],
  864. b_terminations=[front_ports[4]],
  865. )
  866. cable6.clean()
  867. cable6.save()
  868. # Verify forward path: IF1 -> IF2 (both strands through splice)
  869. self.assertPathExists(
  870. (
  871. interfaces[0], cable1, front_ports[0],
  872. rear_ports[0], cable2, rear_ports[1],
  873. [front_ports[1], front_ports[2]], [cable3, cable6], [front_ports[3], front_ports[4]],
  874. rear_ports[2], cable4, rear_ports[3],
  875. front_ports[5], cable5, interfaces[1],
  876. ),
  877. is_complete=True,
  878. is_active=True
  879. )
  880. # Verify reverse path: IF2 -> IF1
  881. self.assertPathExists(
  882. (
  883. interfaces[1], cable5, front_ports[5],
  884. rear_ports[3], cable4, rear_ports[2],
  885. [front_ports[3], front_ports[4]], [cable3, cable6], [front_ports[1], front_ports[2]],
  886. rear_ports[1], cable2, rear_ports[0],
  887. front_ports[0], cable1, interfaces[0],
  888. ),
  889. is_complete=True,
  890. is_active=True
  891. )
  892. self.assertEqual(CablePath.objects.count(), 2)
  893. # Verify cable positions on interfaces
  894. for iface in interfaces:
  895. iface.refresh_from_db()
  896. self.assertEqual(interfaces[0].cable_connector, 1)
  897. self.assertEqual(interfaces[0].cable_positions, [1, 2])
  898. self.assertEqual(interfaces[1].cable_connector, 1)
  899. self.assertEqual(interfaces[1].cable_positions, [1, 2])
  900. # Test SVG generation
  901. CableTraceSVG(interfaces[0]).render()
  902. def test_108_single_interface_two_frontports_unprofiled_through_trunk_with_splices(self):
  903. """
  904. Tests that positions seeded by PortMapping (not cable_positions) are preserved
  905. when crossing profiled cables.
  906. [IF1] -C1- [FP1,FP2][RP1(p=2)] -C2(1C2P)- [RP2(p=2)]
  907. [FP3] -C3- [FP5][RP3(p=2)] -C4(1C2P)- [RP4(p=2)]
  908. [FP7,FP8] -C5- [IF2] / [FP4] -C6- [FP6]
  909. PortMappings: FP1->RP1p1, FP2->RP1p2, FP3->RP2p1, FP4->RP2p2,
  910. FP5->RP3p1, FP6->RP3p2, FP7->RP4p1, FP8->RP4p2
  911. C1 is unprofiled (1 IF -> 2 FPs), C2/C4 are 1C2P trunks,
  912. C3/C6 are unprofiled splices, C5 is unprofiled (2 FPs -> 1 IF).
  913. """
  914. interfaces = [
  915. Interface.objects.create(device=self.device, name='Interface 1'),
  916. Interface.objects.create(device=self.device, name='Interface 2'),
  917. ]
  918. rear_ports = [
  919. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
  920. RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
  921. RearPort.objects.create(device=self.device, name='Rear Port 3', positions=2),
  922. RearPort.objects.create(device=self.device, name='Rear Port 4', positions=2),
  923. ]
  924. front_ports = [
  925. FrontPort.objects.create(device=self.device, name='Front Port 1'), # Panel A strand 1
  926. FrontPort.objects.create(device=self.device, name='Front Port 2'), # Panel A strand 2
  927. FrontPort.objects.create(device=self.device, name='Front Port 3'), # Splice A strand 1
  928. FrontPort.objects.create(device=self.device, name='Front Port 4'), # Splice A strand 2
  929. FrontPort.objects.create(device=self.device, name='Front Port 5'), # Splice B strand 1
  930. FrontPort.objects.create(device=self.device, name='Front Port 6'), # Splice B strand 2
  931. FrontPort.objects.create(device=self.device, name='Front Port 7'), # Panel B strand 1
  932. FrontPort.objects.create(device=self.device, name='Front Port 8'), # Panel B strand 2
  933. ]
  934. PortMapping.objects.bulk_create([
  935. # Panel A: FP1, FP2 -> RP1(pos=2)
  936. PortMapping(
  937. device=self.device, front_port=front_ports[0], front_port_position=1,
  938. rear_port=rear_ports[0], rear_port_position=1,
  939. ),
  940. PortMapping(
  941. device=self.device, front_port=front_ports[1], front_port_position=1,
  942. rear_port=rear_ports[0], rear_port_position=2,
  943. ),
  944. # Splice A: FP3, FP4 -> RP2(pos=2)
  945. PortMapping(
  946. device=self.device, front_port=front_ports[2], front_port_position=1,
  947. rear_port=rear_ports[1], rear_port_position=1,
  948. ),
  949. PortMapping(
  950. device=self.device, front_port=front_ports[3], front_port_position=1,
  951. rear_port=rear_ports[1], rear_port_position=2,
  952. ),
  953. # Splice B: FP5, FP6 -> RP3(pos=2)
  954. PortMapping(
  955. device=self.device, front_port=front_ports[4], front_port_position=1,
  956. rear_port=rear_ports[2], rear_port_position=1,
  957. ),
  958. PortMapping(
  959. device=self.device, front_port=front_ports[5], front_port_position=1,
  960. rear_port=rear_ports[2], rear_port_position=2,
  961. ),
  962. # Panel B: FP7, FP8 -> RP4(pos=2)
  963. PortMapping(
  964. device=self.device, front_port=front_ports[6], front_port_position=1,
  965. rear_port=rear_ports[3], rear_port_position=1,
  966. ),
  967. PortMapping(
  968. device=self.device, front_port=front_ports[7], front_port_position=1,
  969. rear_port=rear_ports[3], rear_port_position=2,
  970. ),
  971. ])
  972. # Create cables
  973. cable1 = Cable(
  974. a_terminations=[interfaces[0]],
  975. b_terminations=[front_ports[0], front_ports[1]],
  976. )
  977. cable1.clean()
  978. cable1.save()
  979. cable2 = Cable(
  980. profile=CableProfileChoices.SINGLE_1C2P,
  981. a_terminations=[rear_ports[0]],
  982. b_terminations=[rear_ports[1]],
  983. )
  984. cable2.clean()
  985. cable2.save()
  986. cable3 = Cable(
  987. a_terminations=[front_ports[2]],
  988. b_terminations=[front_ports[4]],
  989. )
  990. cable3.clean()
  991. cable3.save()
  992. cable4 = Cable(
  993. profile=CableProfileChoices.SINGLE_1C2P,
  994. a_terminations=[rear_ports[2]],
  995. b_terminations=[rear_ports[3]],
  996. )
  997. cable4.clean()
  998. cable4.save()
  999. cable5 = Cable(
  1000. a_terminations=[front_ports[6], front_ports[7]],
  1001. b_terminations=[interfaces[1]],
  1002. )
  1003. cable5.clean()
  1004. cable5.save()
  1005. cable6 = Cable(
  1006. a_terminations=[front_ports[3]],
  1007. b_terminations=[front_ports[5]],
  1008. )
  1009. cable6.clean()
  1010. cable6.save()
  1011. # Verify forward path: IF1 -> IF2 (both strands through splice)
  1012. self.assertPathExists(
  1013. (
  1014. interfaces[0], cable1, [front_ports[0], front_ports[1]],
  1015. rear_ports[0], cable2, rear_ports[1],
  1016. [front_ports[2], front_ports[3]], [cable3, cable6], [front_ports[4], front_ports[5]],
  1017. rear_ports[2], cable4, rear_ports[3],
  1018. [front_ports[6], front_ports[7]], cable5, interfaces[1],
  1019. ),
  1020. is_complete=True,
  1021. is_active=True
  1022. )
  1023. # Verify reverse path: IF2 -> IF1
  1024. self.assertPathExists(
  1025. (
  1026. interfaces[1], cable5, [front_ports[6], front_ports[7]],
  1027. rear_ports[3], cable4, rear_ports[2],
  1028. [front_ports[4], front_ports[5]], [cable3, cable6], [front_ports[2], front_ports[3]],
  1029. rear_ports[1], cable2, rear_ports[0],
  1030. [front_ports[0], front_ports[1]], cable1, interfaces[0],
  1031. ),
  1032. is_complete=True,
  1033. is_active=True
  1034. )
  1035. self.assertEqual(CablePath.objects.count(), 2)
  1036. # Verify cable positions are not set (unprofiled patch cables)
  1037. for iface in interfaces:
  1038. iface.refresh_from_db()
  1039. self.assertIsNone(interfaces[0].cable_connector)
  1040. self.assertIsNone(interfaces[0].cable_positions)
  1041. self.assertIsNone(interfaces[1].cable_connector)
  1042. self.assertIsNone(interfaces[1].cable_positions)
  1043. def test_109_multiconnector_trunk_through_patch_panel(self):
  1044. """
  1045. Tests that a 4-position interface traces correctly through a patch panel
  1046. that fans out to both connectors of a Trunk2C2P cable.
  1047. [IF1] --C1(1C4P)-- [FP1(p=4)][RP1(p=2)] --C3(Trunk2C2P)-- [RP3(p=2)][FP5(p=4)] --C5(1C4P)-- [IF2]
  1048. [RP2(p=2)] [RP4(p=2)]
  1049. PortMappings (Panel A): FP1p1->RP1p1, FP1p2->RP1p2, FP1p3->RP2p1, FP1p4->RP2p2
  1050. PortMappings (Panel B): FP5p1->RP3p1, FP5p2->RP3p2, FP5p3->RP4p1, FP5p4->RP4p2
  1051. """
  1052. interfaces = [
  1053. Interface.objects.create(device=self.device, name='Interface 1'),
  1054. Interface.objects.create(device=self.device, name='Interface 2'),
  1055. ]
  1056. rear_ports = [
  1057. RearPort.objects.create(device=self.device, name='Rear Port 1', positions=2),
  1058. RearPort.objects.create(device=self.device, name='Rear Port 2', positions=2),
  1059. RearPort.objects.create(device=self.device, name='Rear Port 3', positions=2),
  1060. RearPort.objects.create(device=self.device, name='Rear Port 4', positions=2),
  1061. ]
  1062. front_ports = [
  1063. FrontPort.objects.create(device=self.device, name='Front Port 1', positions=4),
  1064. FrontPort.objects.create(device=self.device, name='Front Port 5', positions=4),
  1065. ]
  1066. PortMapping.objects.bulk_create([
  1067. # Panel A: FP1(p=4) -> RP1(p=2) and RP2(p=2)
  1068. PortMapping(
  1069. device=self.device, front_port=front_ports[0], front_port_position=1,
  1070. rear_port=rear_ports[0], rear_port_position=1,
  1071. ),
  1072. PortMapping(
  1073. device=self.device, front_port=front_ports[0], front_port_position=2,
  1074. rear_port=rear_ports[0], rear_port_position=2,
  1075. ),
  1076. PortMapping(
  1077. device=self.device, front_port=front_ports[0], front_port_position=3,
  1078. rear_port=rear_ports[1], rear_port_position=1,
  1079. ),
  1080. PortMapping(
  1081. device=self.device, front_port=front_ports[0], front_port_position=4,
  1082. rear_port=rear_ports[1], rear_port_position=2,
  1083. ),
  1084. # Panel B: FP5(p=4) -> RP3(p=2) and RP4(p=2)
  1085. PortMapping(
  1086. device=self.device, front_port=front_ports[1], front_port_position=1,
  1087. rear_port=rear_ports[2], rear_port_position=1,
  1088. ),
  1089. PortMapping(
  1090. device=self.device, front_port=front_ports[1], front_port_position=2,
  1091. rear_port=rear_ports[2], rear_port_position=2,
  1092. ),
  1093. PortMapping(
  1094. device=self.device, front_port=front_ports[1], front_port_position=3,
  1095. rear_port=rear_ports[3], rear_port_position=1,
  1096. ),
  1097. PortMapping(
  1098. device=self.device, front_port=front_ports[1], front_port_position=4,
  1099. rear_port=rear_ports[3], rear_port_position=2,
  1100. ),
  1101. ])
  1102. # Create cables
  1103. cable1 = Cable(
  1104. profile=CableProfileChoices.SINGLE_1C4P,
  1105. a_terminations=[interfaces[0]],
  1106. b_terminations=[front_ports[0]],
  1107. )
  1108. cable1.clean()
  1109. cable1.save()
  1110. cable3 = Cable(
  1111. profile=CableProfileChoices.TRUNK_2C2P,
  1112. a_terminations=[rear_ports[0], rear_ports[1]],
  1113. b_terminations=[rear_ports[2], rear_ports[3]],
  1114. )
  1115. cable3.clean()
  1116. cable3.save()
  1117. cable5 = Cable(
  1118. profile=CableProfileChoices.SINGLE_1C4P,
  1119. a_terminations=[front_ports[1]],
  1120. b_terminations=[interfaces[1]],
  1121. )
  1122. cable5.clean()
  1123. cable5.save()
  1124. # Verify forward path: IF1 -> IF2 (all 4 positions through trunk)
  1125. self.assertPathExists(
  1126. (
  1127. interfaces[0], cable1, front_ports[0],
  1128. [rear_ports[0], rear_ports[1]], cable3, [rear_ports[2], rear_ports[3]],
  1129. front_ports[1], cable5, interfaces[1],
  1130. ),
  1131. is_complete=True,
  1132. is_active=True
  1133. )
  1134. # Verify reverse path: IF2 -> IF1
  1135. self.assertPathExists(
  1136. (
  1137. interfaces[1], cable5, front_ports[1],
  1138. [rear_ports[2], rear_ports[3]], cable3, [rear_ports[0], rear_ports[1]],
  1139. front_ports[0], cable1, interfaces[0],
  1140. ),
  1141. is_complete=True,
  1142. is_active=True
  1143. )
  1144. self.assertEqual(CablePath.objects.count(), 2)
  1145. # Verify cable positions
  1146. for iface in interfaces:
  1147. iface.refresh_from_db()
  1148. self.assertEqual(interfaces[0].cable_connector, 1)
  1149. self.assertEqual(interfaces[0].cable_positions, [1, 2, 3, 4])
  1150. self.assertEqual(interfaces[1].cable_connector, 1)
  1151. self.assertEqual(interfaces[1].cable_positions, [1, 2, 3, 4])
  1152. # Verify rear port connector assignments
  1153. for rp in rear_ports:
  1154. rp.refresh_from_db()
  1155. self.assertEqual(rear_ports[0].cable_connector, 1)
  1156. self.assertEqual(rear_ports[0].cable_positions, [1, 2])
  1157. self.assertEqual(rear_ports[1].cable_connector, 2)
  1158. self.assertEqual(rear_ports[1].cable_positions, [1, 2])
  1159. self.assertEqual(rear_ports[2].cable_connector, 1)
  1160. self.assertEqual(rear_ports[2].cable_positions, [1, 2])
  1161. self.assertEqual(rear_ports[3].cable_connector, 2)
  1162. self.assertEqual(rear_ports[3].cable_positions, [1, 2])
  1163. # Test SVG generation
  1164. CableTraceSVG(interfaces[0]).render()
  1165. def test_110_partial_termination_profiled_trunk(self):
  1166. """
  1167. Tests that tracing through a partially terminated profiled cable
  1168. produces a complete path for the connected pair and an incomplete
  1169. path for the unconnected pair, without errors. Also verifies that
  1170. attaching the missing termination completes the previously incomplete path.
  1171. [IF1] --C1-- [IF3]
  1172. [IF2] (empty)
  1173. Cable profile: Trunk 2C1P with only one B-side termination.
  1174. """
  1175. interfaces = [
  1176. Interface.objects.create(device=self.device, name='Interface 1'),
  1177. Interface.objects.create(device=self.device, name='Interface 2'),
  1178. Interface.objects.create(device=self.device, name='Interface 3'),
  1179. ]
  1180. # Create a 2-connector trunk cable with both A-side connectors
  1181. # populated but only one B-side connector terminated.
  1182. cable1 = Cable(
  1183. profile=CableProfileChoices.TRUNK_2C1P,
  1184. a_terminations=[interfaces[0], interfaces[1]],
  1185. b_terminations=[interfaces[2]],
  1186. )
  1187. cable1.clean()
  1188. cable1.save()
  1189. # IF1 (connector 1) → IF3 (connector 1): complete path
  1190. path1 = self.assertPathExists(
  1191. (interfaces[0], cable1, interfaces[2]),
  1192. is_complete=True,
  1193. is_active=True,
  1194. )
  1195. # IF3 (connector 1) → IF1 (connector 1): complete path (reverse)
  1196. path2 = self.assertPathExists(
  1197. (interfaces[2], cable1, interfaces[0]),
  1198. is_complete=True,
  1199. is_active=True,
  1200. )
  1201. # IF2 (connector 2) has no B-side peer.
  1202. # Tracing should stop at this segment, and the resulting path
  1203. # should remain incomplete.
  1204. # Verify via the origin's _path reference rather than matching
  1205. # the exact path shape directly.
  1206. interfaces[1].refresh_from_db()
  1207. self.assertIsNotNone(interfaces[1]._path_id)
  1208. path3 = CablePath.objects.get(pk=interfaces[1]._path_id)
  1209. self.assertFalse(path3.is_complete)
  1210. self.assertTrue(path3.is_active)
  1211. for iface in interfaces:
  1212. iface.refresh_from_db()
  1213. self.assertPathIsSet(interfaces[0], path1)
  1214. self.assertPathIsSet(interfaces[2], path2)
  1215. self.assertPathIsSet(interfaces[1], path3)
  1216. # Verify connector/position assignments
  1217. self.assertEqual(interfaces[0].cable_connector, 1)
  1218. self.assertEqual(interfaces[0].cable_positions, [1])
  1219. self.assertEqual(interfaces[1].cable_connector, 2)
  1220. self.assertEqual(interfaces[1].cable_positions, [1])
  1221. self.assertEqual(interfaces[2].cable_connector, 1)
  1222. self.assertEqual(interfaces[2].cable_positions, [1])
  1223. # Now attach the missing B-side termination and verify the
  1224. # previously incomplete path becomes complete.
  1225. interface4 = Interface.objects.create(device=self.device, name='Interface 4')
  1226. cable1.b_terminations = [interfaces[2], interface4]
  1227. cable1.clean()
  1228. cable1.save()
  1229. path4 = self.assertPathExists(
  1230. (interfaces[1], cable1, interface4),
  1231. is_complete=True,
  1232. is_active=True,
  1233. )
  1234. path5 = self.assertPathExists(
  1235. (interface4, cable1, interfaces[1]),
  1236. is_complete=True,
  1237. is_active=True,
  1238. )
  1239. interfaces[1].refresh_from_db()
  1240. interface4.refresh_from_db()
  1241. self.assertPathIsSet(interfaces[1], path4)
  1242. self.assertPathIsSet(interface4, path5)
  1243. self.assertEqual(interface4.cable_connector, 2)
  1244. self.assertEqual(interface4.cable_positions, [1])
  1245. def test_202_single_path_via_pass_through_with_breakouts(self):
  1246. """
  1247. [IF1] --C1-- [FP1] [RP1] --C2-- [IF3]
  1248. [IF2] [IF4]
  1249. """
  1250. interfaces = [
  1251. Interface.objects.create(device=self.device, name='Interface 1'),
  1252. Interface.objects.create(device=self.device, name='Interface 2'),
  1253. Interface.objects.create(device=self.device, name='Interface 3'),
  1254. Interface.objects.create(device=self.device, name='Interface 4'),
  1255. ]
  1256. rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=4)
  1257. frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1', positions=4)
  1258. PortMapping.objects.bulk_create([
  1259. PortMapping(
  1260. device=self.device,
  1261. front_port=frontport1,
  1262. front_port_position=1,
  1263. rear_port=rearport1,
  1264. rear_port_position=1,
  1265. ),
  1266. PortMapping(
  1267. device=self.device,
  1268. front_port=frontport1,
  1269. front_port_position=2,
  1270. rear_port=rearport1,
  1271. rear_port_position=2,
  1272. ),
  1273. PortMapping(
  1274. device=self.device,
  1275. front_port=frontport1,
  1276. front_port_position=3,
  1277. rear_port=rearport1,
  1278. rear_port_position=3,
  1279. ),
  1280. PortMapping(
  1281. device=self.device,
  1282. front_port=frontport1,
  1283. front_port_position=4,
  1284. rear_port=rearport1,
  1285. rear_port_position=4,
  1286. ),
  1287. ])
  1288. # Create cables
  1289. cable1 = Cable(
  1290. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1291. a_terminations=[frontport1],
  1292. b_terminations=[interfaces[0], interfaces[1]],
  1293. )
  1294. cable1.clean()
  1295. cable1.save()
  1296. cable2 = Cable(
  1297. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1298. a_terminations=[rearport1],
  1299. b_terminations=[interfaces[2], interfaces[3]]
  1300. )
  1301. cable2.clean()
  1302. cable2.save()
  1303. paths = [
  1304. self.assertPathExists(
  1305. (interfaces[0], cable1, frontport1, rearport1, cable2, interfaces[2]),
  1306. is_complete=True,
  1307. is_active=True
  1308. ),
  1309. self.assertPathExists(
  1310. (interfaces[1], cable1, frontport1, rearport1, cable2, interfaces[3]),
  1311. is_complete=True,
  1312. is_active=True
  1313. ),
  1314. self.assertPathExists(
  1315. (interfaces[2], cable2, rearport1, frontport1, cable1, interfaces[0]),
  1316. is_complete=True,
  1317. is_active=True
  1318. ),
  1319. self.assertPathExists(
  1320. (interfaces[3], cable2, rearport1, frontport1, cable1, interfaces[1]),
  1321. is_complete=True,
  1322. is_active=True
  1323. ),
  1324. ]
  1325. self.assertEqual(CablePath.objects.count(), 4)
  1326. for interface in interfaces:
  1327. interface.refresh_from_db()
  1328. self.assertPathIsSet(interfaces[0], paths[0])
  1329. self.assertPathIsSet(interfaces[1], paths[1])
  1330. self.assertPathIsSet(interfaces[2], paths[2])
  1331. self.assertPathIsSet(interfaces[3], paths[3])
  1332. # Test SVG generation
  1333. CableTraceSVG(interfaces[0]).render()
  1334. def test_204_multiple_paths_via_pass_through_with_breakouts(self):
  1335. """
  1336. [IF1] --C1-- [FP1] [RP1] --C3-- [RP2] [FP3] --C4-- [IF5]
  1337. [IF2] [IF6]
  1338. [IF3] --C2-- [FP2] [FP4] --C5-- [IF7]
  1339. [IF4] [IF8]
  1340. """
  1341. interfaces = [
  1342. Interface.objects.create(device=self.device, name='Interface 1'),
  1343. Interface.objects.create(device=self.device, name='Interface 2'),
  1344. Interface.objects.create(device=self.device, name='Interface 3'),
  1345. Interface.objects.create(device=self.device, name='Interface 4'),
  1346. Interface.objects.create(device=self.device, name='Interface 5'),
  1347. Interface.objects.create(device=self.device, name='Interface 6'),
  1348. Interface.objects.create(device=self.device, name='Interface 7'),
  1349. Interface.objects.create(device=self.device, name='Interface 8'),
  1350. ]
  1351. rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1', positions=8)
  1352. rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2', positions=8)
  1353. frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1:1', positions=4)
  1354. frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 1:2', positions=4)
  1355. frontport3 = FrontPort.objects.create(device=self.device, name='Front Port 2:1', positions=4)
  1356. frontport4 = FrontPort.objects.create(device=self.device, name='Front Port 2:2', positions=4)
  1357. PortMapping.objects.bulk_create([
  1358. PortMapping(
  1359. device=self.device,
  1360. front_port=frontport1,
  1361. front_port_position=1,
  1362. rear_port=rearport1,
  1363. rear_port_position=1,
  1364. ),
  1365. PortMapping(
  1366. device=self.device,
  1367. front_port=frontport1,
  1368. front_port_position=2,
  1369. rear_port=rearport1,
  1370. rear_port_position=2,
  1371. ),
  1372. PortMapping(
  1373. device=self.device,
  1374. front_port=frontport2,
  1375. front_port_position=1,
  1376. rear_port=rearport1,
  1377. rear_port_position=5,
  1378. ),
  1379. PortMapping(
  1380. device=self.device,
  1381. front_port=frontport2,
  1382. front_port_position=2,
  1383. rear_port=rearport1,
  1384. rear_port_position=6,
  1385. ),
  1386. PortMapping(
  1387. device=self.device,
  1388. front_port=frontport3,
  1389. front_port_position=1,
  1390. rear_port=rearport2,
  1391. rear_port_position=1,
  1392. ),
  1393. PortMapping(
  1394. device=self.device,
  1395. front_port=frontport3,
  1396. front_port_position=2,
  1397. rear_port=rearport2,
  1398. rear_port_position=2,
  1399. ),
  1400. PortMapping(
  1401. device=self.device,
  1402. front_port=frontport4,
  1403. front_port_position=1,
  1404. rear_port=rearport2,
  1405. rear_port_position=5,
  1406. ),
  1407. PortMapping(
  1408. device=self.device,
  1409. front_port=frontport4,
  1410. front_port_position=2,
  1411. rear_port=rearport2,
  1412. rear_port_position=6,
  1413. ),
  1414. ])
  1415. # Create cables
  1416. cable1 = Cable(
  1417. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1418. a_terminations=[frontport1],
  1419. b_terminations=[interfaces[0], interfaces[1]],
  1420. )
  1421. cable1.clean()
  1422. cable1.save()
  1423. cable2 = Cable(
  1424. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1425. a_terminations=[frontport2],
  1426. b_terminations=[interfaces[2], interfaces[3]],
  1427. )
  1428. cable2.clean()
  1429. cable2.save()
  1430. cable3 = Cable(
  1431. profile=CableProfileChoices.SINGLE_1C8P,
  1432. a_terminations=[rearport1],
  1433. b_terminations=[rearport2]
  1434. )
  1435. cable3.clean()
  1436. cable3.save()
  1437. cable4 = Cable(
  1438. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1439. a_terminations=[frontport3],
  1440. b_terminations=[interfaces[4], interfaces[5]],
  1441. )
  1442. cable4.clean()
  1443. cable4.save()
  1444. cable5 = Cable(
  1445. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1446. a_terminations=[frontport4],
  1447. b_terminations=[interfaces[6], interfaces[7]],
  1448. )
  1449. cable5.clean()
  1450. cable5.save()
  1451. paths = [
  1452. self.assertPathExists(
  1453. (
  1454. interfaces[0], cable1, frontport1, rearport1, cable3, rearport2, frontport3, cable4,
  1455. interfaces[4],
  1456. ),
  1457. is_complete=True,
  1458. is_active=True,
  1459. ),
  1460. self.assertPathExists(
  1461. (
  1462. interfaces[1], cable1, frontport1, rearport1, cable3, rearport2, frontport3, cable4,
  1463. interfaces[5],
  1464. ),
  1465. is_complete=True,
  1466. is_active=True,
  1467. ),
  1468. self.assertPathExists(
  1469. (
  1470. interfaces[2], cable2, frontport2, rearport1, cable3, rearport2, frontport4, cable5,
  1471. interfaces[6],
  1472. ),
  1473. is_complete=True,
  1474. is_active=True,
  1475. ),
  1476. self.assertPathExists(
  1477. (
  1478. interfaces[3], cable2, frontport2, rearport1, cable3, rearport2, frontport4, cable5,
  1479. interfaces[7],
  1480. ),
  1481. is_complete=True,
  1482. is_active=True,
  1483. ),
  1484. self.assertPathExists(
  1485. (
  1486. interfaces[4], cable4, frontport3, rearport2, cable3, rearport1, frontport1, cable1,
  1487. interfaces[0],
  1488. ),
  1489. is_complete=True,
  1490. is_active=True,
  1491. ),
  1492. self.assertPathExists(
  1493. (
  1494. interfaces[5], cable4, frontport3, rearport2, cable3, rearport1, frontport1, cable1,
  1495. interfaces[1],
  1496. ),
  1497. is_complete=True,
  1498. is_active=True,
  1499. ),
  1500. self.assertPathExists(
  1501. (
  1502. interfaces[6], cable5, frontport4, rearport2, cable3, rearport1, frontport2, cable2,
  1503. interfaces[2],
  1504. ),
  1505. is_complete=True,
  1506. is_active=True,
  1507. ),
  1508. self.assertPathExists(
  1509. (
  1510. interfaces[7], cable5, frontport4, rearport2, cable3, rearport1, frontport2, cable2,
  1511. interfaces[3],
  1512. ),
  1513. is_complete=True,
  1514. is_active=True,
  1515. ),
  1516. ]
  1517. self.assertEqual(CablePath.objects.count(), 8)
  1518. for interface in interfaces:
  1519. interface.refresh_from_db()
  1520. self.assertPathIsSet(interfaces[0], paths[0])
  1521. self.assertPathIsSet(interfaces[1], paths[1])
  1522. self.assertPathIsSet(interfaces[2], paths[2])
  1523. self.assertPathIsSet(interfaces[3], paths[3])
  1524. self.assertPathIsSet(interfaces[4], paths[4])
  1525. self.assertPathIsSet(interfaces[5], paths[5])
  1526. self.assertPathIsSet(interfaces[6], paths[6])
  1527. self.assertPathIsSet(interfaces[7], paths[7])
  1528. # Test SVG generation
  1529. CableTraceSVG(interfaces[0]).render()
  1530. def test_212_interface_to_interface_via_circuit_with_breakouts(self):
  1531. """
  1532. [IF1] --C1-- [CT1] [CT2] --C2-- [IF3]
  1533. [IF2] [IF4]
  1534. """
  1535. interfaces = [
  1536. Interface.objects.create(device=self.device, name='Interface 1'),
  1537. Interface.objects.create(device=self.device, name='Interface 2'),
  1538. Interface.objects.create(device=self.device, name='Interface 3'),
  1539. Interface.objects.create(device=self.device, name='Interface 4'),
  1540. ]
  1541. circuittermination1 = CircuitTermination.objects.create(
  1542. circuit=self.circuit,
  1543. termination=self.site,
  1544. term_side='A'
  1545. )
  1546. circuittermination2 = CircuitTermination.objects.create(
  1547. circuit=self.circuit,
  1548. termination=self.site,
  1549. term_side='Z'
  1550. )
  1551. # Create cables
  1552. cable1 = Cable(
  1553. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1554. a_terminations=[circuittermination1],
  1555. b_terminations=[interfaces[0], interfaces[1]],
  1556. )
  1557. cable1.clean()
  1558. cable1.save()
  1559. cable2 = Cable(
  1560. profile=CableProfileChoices.BREAKOUT_1C4P_4C1P,
  1561. a_terminations=[circuittermination2],
  1562. b_terminations=[interfaces[2], interfaces[3]]
  1563. )
  1564. cable2.clean()
  1565. cable2.save()
  1566. # Check for two complete paths in either direction
  1567. paths = [
  1568. self.assertPathExists(
  1569. (interfaces[0], cable1, circuittermination1, circuittermination2, cable2, interfaces[2]),
  1570. is_complete=True,
  1571. is_active=True,
  1572. ),
  1573. self.assertPathExists(
  1574. (interfaces[1], cable1, circuittermination1, circuittermination2, cable2, interfaces[3]),
  1575. is_complete=True,
  1576. is_active=True,
  1577. ),
  1578. self.assertPathExists(
  1579. (interfaces[2], cable2, circuittermination2, circuittermination1, cable1, interfaces[0]),
  1580. is_complete=True,
  1581. is_active=True,
  1582. ),
  1583. self.assertPathExists(
  1584. (interfaces[3], cable2, circuittermination2, circuittermination1, cable1, interfaces[1]),
  1585. is_complete=True,
  1586. is_active=True,
  1587. ),
  1588. ]
  1589. self.assertEqual(CablePath.objects.count(), 4)
  1590. for interface in interfaces:
  1591. interface.refresh_from_db()
  1592. self.assertPathIsSet(interfaces[0], paths[0])
  1593. self.assertPathIsSet(interfaces[1], paths[1])
  1594. self.assertPathIsSet(interfaces[2], paths[2])
  1595. self.assertPathIsSet(interfaces[3], paths[3])
  1596. # Test SVG generation
  1597. CableTraceSVG(interfaces[0]).render()
  1598. # TBD: Is this a topology we want to support?
  1599. @skip("Test applicability TBD")
  1600. def test_217_interface_to_interface_via_rear_ports(self):
  1601. """
  1602. [IF1] --C1-- [FP1] [RP1] --C2-- [RP3] [FP3] --C3-- [IF2]
  1603. [FP2] [RP2] [RP4] [FP4]
  1604. """
  1605. interfaces = [
  1606. Interface.objects.create(device=self.device, name='Interface 1'),
  1607. Interface.objects.create(device=self.device, name='Interface 2'),
  1608. ]
  1609. rear_ports = [
  1610. RearPort.objects.create(device=self.device, name='Rear Port 1'),
  1611. RearPort.objects.create(device=self.device, name='Rear Port 2'),
  1612. RearPort.objects.create(device=self.device, name='Rear Port 3'),
  1613. RearPort.objects.create(device=self.device, name='Rear Port 4'),
  1614. ]
  1615. front_ports = [
  1616. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  1617. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  1618. FrontPort.objects.create(device=self.device, name='Front Port 3'),
  1619. FrontPort.objects.create(device=self.device, name='Front Port 4'),
  1620. ]
  1621. PortMapping.objects.bulk_create([
  1622. PortMapping(
  1623. device=self.device,
  1624. front_port=front_ports[0],
  1625. front_port_position=1,
  1626. rear_port=rear_ports[0],
  1627. rear_port_position=1,
  1628. ),
  1629. PortMapping(
  1630. device=self.device,
  1631. front_port=front_ports[1],
  1632. front_port_position=1,
  1633. rear_port=rear_ports[1],
  1634. rear_port_position=1,
  1635. ),
  1636. PortMapping(
  1637. device=self.device,
  1638. front_port=front_ports[2],
  1639. front_port_position=1,
  1640. rear_port=rear_ports[2],
  1641. rear_port_position=1,
  1642. ),
  1643. PortMapping(
  1644. device=self.device,
  1645. front_port=front_ports[3],
  1646. front_port_position=1,
  1647. rear_port=rear_ports[3],
  1648. rear_port_position=1,
  1649. ),
  1650. ])
  1651. # Create cables
  1652. cable1 = Cable(
  1653. profile=CableProfileChoices.SINGLE_2C1P,
  1654. a_terminations=[interfaces[0]],
  1655. b_terminations=[front_ports[0], front_ports[1]]
  1656. )
  1657. cable1.clean()
  1658. cable1.save()
  1659. cable2 = Cable(
  1660. a_terminations=[rear_ports[0], rear_ports[1]],
  1661. b_terminations=[rear_ports[2], rear_ports[3]]
  1662. )
  1663. cable2.clean()
  1664. cable2.save()
  1665. cable3 = Cable(
  1666. profile=CableProfileChoices.SINGLE_2C1P,
  1667. a_terminations=[interfaces[1]],
  1668. b_terminations=[front_ports[2], front_ports[3]]
  1669. )
  1670. cable3.clean()
  1671. cable3.save()
  1672. # Check for one complete path in either direction
  1673. paths = [
  1674. self.assertPathExists(
  1675. (
  1676. interfaces[0], cable1, (front_ports[0], front_ports[1]), (rear_ports[0], rear_ports[1]), cable2,
  1677. (rear_ports[2], rear_ports[3]), (front_ports[2], front_ports[3]), cable3, interfaces[1]
  1678. ),
  1679. is_complete=True
  1680. ),
  1681. self.assertPathExists(
  1682. (
  1683. interfaces[1], cable3, (front_ports[2], front_ports[3]), (rear_ports[2], rear_ports[3]), cable2,
  1684. (rear_ports[0], rear_ports[1]), (front_ports[0], front_ports[1]), cable1, interfaces[0]
  1685. ),
  1686. is_complete=True
  1687. ),
  1688. ]
  1689. self.assertEqual(CablePath.objects.count(), 2)
  1690. for interface in interfaces:
  1691. interface.refresh_from_db()
  1692. self.assertPathIsSet(interfaces[0], paths[0])
  1693. self.assertPathIsSet(interfaces[1], paths[1])
  1694. # Test SVG generation
  1695. CableTraceSVG(interfaces[0]).render()
  1696. def test_223_single_path_via_multiple_pass_throughs_with_breakouts(self):
  1697. """
  1698. [IF1] --C1-- [FP1] [RP1] --C2-- [IF3]
  1699. [IF2] [FP2] [RP2] [IF4]
  1700. """
  1701. interfaces = [
  1702. Interface.objects.create(device=self.device, name='Interface 1'),
  1703. Interface.objects.create(device=self.device, name='Interface 2'),
  1704. Interface.objects.create(device=self.device, name='Interface 3'),
  1705. Interface.objects.create(device=self.device, name='Interface 4'),
  1706. ]
  1707. rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
  1708. rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
  1709. frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
  1710. frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
  1711. PortMapping.objects.bulk_create([
  1712. PortMapping(
  1713. device=self.device,
  1714. front_port=frontport1,
  1715. front_port_position=1,
  1716. rear_port=rearport1,
  1717. rear_port_position=1,
  1718. ),
  1719. PortMapping(
  1720. device=self.device,
  1721. front_port=frontport2,
  1722. front_port_position=1,
  1723. rear_port=rearport2,
  1724. rear_port_position=1,
  1725. ),
  1726. ])
  1727. # Create cables
  1728. cable1 = Cable(
  1729. profile=CableProfileChoices.TRUNK_2C2P,
  1730. a_terminations=[interfaces[0], interfaces[1]],
  1731. b_terminations=[frontport1, frontport2]
  1732. )
  1733. cable1.clean()
  1734. cable1.save()
  1735. cable2 = Cable(
  1736. profile=CableProfileChoices.TRUNK_2C2P,
  1737. a_terminations=[rearport1, rearport2],
  1738. b_terminations=[interfaces[2], interfaces[3]]
  1739. )
  1740. cable2.clean()
  1741. cable2.save()
  1742. # Validate paths
  1743. self.assertPathExists(
  1744. (interfaces[0], cable1, frontport1, rearport1, cable2, interfaces[2]),
  1745. is_complete=True,
  1746. is_active=True
  1747. )
  1748. self.assertPathExists(
  1749. (interfaces[1], cable1, frontport2, rearport2, cable2, interfaces[3]),
  1750. is_complete=True,
  1751. is_active=True
  1752. )
  1753. self.assertPathExists(
  1754. (interfaces[2], cable2, rearport1, frontport1, cable1, interfaces[0]),
  1755. is_complete=True,
  1756. is_active=True
  1757. )
  1758. self.assertPathExists(
  1759. (interfaces[3], cable2, rearport2, frontport2, cable1, interfaces[1]),
  1760. is_complete=True,
  1761. is_active=True
  1762. )
  1763. self.assertEqual(CablePath.objects.count(), 4)
  1764. def test_225_breakout_1c2p_2c1p_to_single_position_passthroughs(self):
  1765. """
  1766. Regression test for #22187: a 1C2P:2C1P breakout cable terminating on two
  1767. single-position FrontPorts (each mapping 1:1 to a single-position RearPort)
  1768. which are then connected to PathEndpoints via unprofiled cables.
  1769. [IF1] --C1 (1C2P:2C1P)-- [FP1] [RP1] --C2 (unprofiled)-- [IF2]
  1770. [FP2] [RP2] --C3 (unprofiled)-- [IF3]
  1771. """
  1772. interfaces = [
  1773. Interface.objects.create(device=self.device, name='Interface 1'),
  1774. Interface.objects.create(device=self.device, name='Interface 2'),
  1775. Interface.objects.create(device=self.device, name='Interface 3'),
  1776. ]
  1777. rear_ports = [
  1778. RearPort.objects.create(device=self.device, name='Rear Port 1'),
  1779. RearPort.objects.create(device=self.device, name='Rear Port 2'),
  1780. ]
  1781. front_ports = [
  1782. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  1783. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  1784. ]
  1785. PortMapping.objects.bulk_create([
  1786. PortMapping(
  1787. device=self.device,
  1788. front_port=front_ports[0],
  1789. front_port_position=1,
  1790. rear_port=rear_ports[0],
  1791. rear_port_position=1,
  1792. ),
  1793. PortMapping(
  1794. device=self.device,
  1795. front_port=front_ports[1],
  1796. front_port_position=1,
  1797. rear_port=rear_ports[1],
  1798. rear_port_position=1,
  1799. ),
  1800. ])
  1801. # Create cables
  1802. cable1 = Cable(
  1803. profile=CableProfileChoices.BREAKOUT_1C2P_2C1P,
  1804. a_terminations=[interfaces[0]],
  1805. b_terminations=[front_ports[0], front_ports[1]],
  1806. )
  1807. cable1.clean()
  1808. cable1.save()
  1809. cable2 = Cable(
  1810. a_terminations=[rear_ports[0]],
  1811. b_terminations=[interfaces[1]],
  1812. )
  1813. cable2.clean()
  1814. cable2.save()
  1815. cable3 = Cable(
  1816. a_terminations=[rear_ports[1]],
  1817. b_terminations=[interfaces[2]],
  1818. )
  1819. cable3.clean()
  1820. cable3.save()
  1821. # The breakout splits IF1's two positions into separate downstream cables,
  1822. # so the forward path has FP1/FP2, RP1/RP2, and the two unprofiled cables
  1823. # at the corresponding hops.
  1824. path1 = self.assertPathExists(
  1825. (
  1826. interfaces[0],
  1827. cable1,
  1828. [front_ports[0], front_ports[1]],
  1829. [rear_ports[0], rear_ports[1]],
  1830. [cable2, cable3],
  1831. [interfaces[1], interfaces[2]],
  1832. ),
  1833. is_complete=True,
  1834. is_active=True,
  1835. )
  1836. path2 = self.assertPathExists(
  1837. (interfaces[1], cable2, rear_ports[0], front_ports[0], cable1, interfaces[0]),
  1838. is_complete=True,
  1839. is_active=True,
  1840. )
  1841. path3 = self.assertPathExists(
  1842. (interfaces[2], cable3, rear_ports[1], front_ports[1], cable1, interfaces[0]),
  1843. is_complete=True,
  1844. is_active=True,
  1845. )
  1846. self.assertEqual(CablePath.objects.count(), 3)
  1847. for interface in interfaces:
  1848. interface.refresh_from_db()
  1849. self.assertPathIsSet(interfaces[0], path1)
  1850. self.assertPathIsSet(interfaces[1], path2)
  1851. self.assertPathIsSet(interfaces[2], path3)
  1852. # Test SVG generation from both directions
  1853. CableTraceSVG(interfaces[0]).render()
  1854. CableTraceSVG(interfaces[1]).render()
  1855. CableTraceSVG(interfaces[2]).render()
  1856. def test_226_total_length_via_circuit(self):
  1857. """
  1858. [IF1] --C1-- [CT1] [CT2] --C2-- [IF2]
  1859. """
  1860. self.circuit.distance = 10
  1861. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  1862. self.circuit.save()
  1863. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  1864. interface2 = Interface.objects.create(device=self.device, name='Interface 2')
  1865. circuittermination1 = CircuitTermination.objects.create(
  1866. circuit=self.circuit,
  1867. termination=self.site,
  1868. term_side='A'
  1869. )
  1870. circuittermination2 = CircuitTermination.objects.create(
  1871. circuit=self.circuit,
  1872. termination=self.site,
  1873. term_side='Z'
  1874. )
  1875. # Create cables
  1876. cable1 = Cable(
  1877. profile=CableProfileChoices.SINGLE_1C1P,
  1878. length=200,
  1879. length_unit=CableLengthUnitChoices.UNIT_METER,
  1880. a_terminations=[interface1],
  1881. b_terminations=[circuittermination1]
  1882. )
  1883. cable1.clean()
  1884. cable1.save()
  1885. cable2 = Cable(
  1886. profile=CableProfileChoices.SINGLE_1C1P,
  1887. length=200,
  1888. length_unit=CableLengthUnitChoices.UNIT_METER,
  1889. a_terminations=[circuittermination2],
  1890. b_terminations=[interface2]
  1891. )
  1892. cable2.clean()
  1893. cable2.save()
  1894. # Check for complete paths in both directions
  1895. paths = [
  1896. self.assertPathExists(
  1897. (interface1, cable1, circuittermination1, circuittermination2, cable2, interface2),
  1898. is_complete=True,
  1899. is_active=True
  1900. ),
  1901. self.assertPathExists(
  1902. (interface2, cable2, circuittermination2, circuittermination1, cable1, interface1),
  1903. is_complete=True,
  1904. is_active=True
  1905. ),
  1906. ]
  1907. # The crossed circuit's distance counts toward the total path length
  1908. for path in paths:
  1909. self.assertEqual(path.get_total_length(), (10400, True))
  1910. # An unset circuit distance makes the total length non-definitive
  1911. self.circuit.distance = None
  1912. self.circuit.save()
  1913. for path in paths:
  1914. self.assertEqual(path.get_total_length(), (400, False))
  1915. # A circuit distance of zero is a known value and remains definitive
  1916. self.circuit.distance = 0
  1917. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  1918. self.circuit.save()
  1919. for path in paths:
  1920. self.assertEqual(path.get_total_length(), (400, True))
  1921. def test_227_total_length_via_circuit_without_peer_termination(self):
  1922. """
  1923. [IF1] --C1-- [CT1]
  1924. """
  1925. self.circuit.distance = 10
  1926. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  1927. self.circuit.save()
  1928. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  1929. circuittermination1 = CircuitTermination.objects.create(
  1930. circuit=self.circuit,
  1931. termination=self.site,
  1932. term_side='A'
  1933. )
  1934. cable1 = Cable(
  1935. profile=CableProfileChoices.SINGLE_1C1P,
  1936. length=200,
  1937. length_unit=CableLengthUnitChoices.UNIT_METER,
  1938. a_terminations=[interface1],
  1939. b_terminations=[circuittermination1]
  1940. )
  1941. cable1.clean()
  1942. cable1.save()
  1943. # The distance of a circuit which the path does not cross is excluded
  1944. path = self.assertPathExists(
  1945. (interface1, cable1, circuittermination1),
  1946. is_complete=False
  1947. )
  1948. self.assertEqual(path.get_total_length(), (200, True))
  1949. def test_228_total_length_via_multiple_circuits(self):
  1950. """
  1951. [IF1] --C1-- [CT1] [CT2] --C2-- [CT3] [CT4] --C3-- [IF2]
  1952. """
  1953. self.circuit.distance = 10
  1954. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  1955. self.circuit.save()
  1956. circuit2 = Circuit.objects.create(
  1957. provider=self.circuit.provider,
  1958. type=self.circuit.type,
  1959. cid='Circuit 2',
  1960. distance=5,
  1961. distance_unit=DistanceUnitChoices.UNIT_KILOMETER
  1962. )
  1963. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  1964. interface2 = Interface.objects.create(device=self.device, name='Interface 2')
  1965. circuittermination1 = CircuitTermination.objects.create(
  1966. circuit=self.circuit,
  1967. termination=self.site,
  1968. term_side='A'
  1969. )
  1970. circuittermination2 = CircuitTermination.objects.create(
  1971. circuit=self.circuit,
  1972. termination=self.site,
  1973. term_side='Z'
  1974. )
  1975. circuittermination3 = CircuitTermination.objects.create(
  1976. circuit=circuit2,
  1977. termination=self.site,
  1978. term_side='A'
  1979. )
  1980. circuittermination4 = CircuitTermination.objects.create(
  1981. circuit=circuit2,
  1982. termination=self.site,
  1983. term_side='Z'
  1984. )
  1985. # Create cables
  1986. cable1 = Cable(
  1987. profile=CableProfileChoices.SINGLE_1C1P,
  1988. length=200,
  1989. length_unit=CableLengthUnitChoices.UNIT_METER,
  1990. a_terminations=[interface1],
  1991. b_terminations=[circuittermination1]
  1992. )
  1993. cable1.clean()
  1994. cable1.save()
  1995. cable2 = Cable(
  1996. profile=CableProfileChoices.SINGLE_1C1P,
  1997. length=100,
  1998. length_unit=CableLengthUnitChoices.UNIT_METER,
  1999. a_terminations=[circuittermination2],
  2000. b_terminations=[circuittermination3]
  2001. )
  2002. cable2.clean()
  2003. cable2.save()
  2004. cable3 = Cable(
  2005. profile=CableProfileChoices.SINGLE_1C1P,
  2006. length=200,
  2007. length_unit=CableLengthUnitChoices.UNIT_METER,
  2008. a_terminations=[circuittermination4],
  2009. b_terminations=[interface2]
  2010. )
  2011. cable3.clean()
  2012. cable3.save()
  2013. # Check for complete paths in both directions
  2014. paths = [
  2015. self.assertPathExists(
  2016. (
  2017. interface1, cable1, circuittermination1, circuittermination2, cable2, circuittermination3,
  2018. circuittermination4, cable3, interface2,
  2019. ),
  2020. is_complete=True,
  2021. is_active=True
  2022. ),
  2023. self.assertPathExists(
  2024. (
  2025. interface2, cable3, circuittermination4, circuittermination3, cable2, circuittermination2,
  2026. circuittermination1, cable1, interface1,
  2027. ),
  2028. is_complete=True,
  2029. is_active=True
  2030. ),
  2031. ]
  2032. # Each crossed circuit adds its distance to the total path length
  2033. for path in paths:
  2034. self.assertEqual(path.get_total_length(), (15500, True))
  2035. def test_229_total_length_via_circuit_to_site(self):
  2036. """
  2037. [IF1] --C1-- [CT1] [CT2] --> [Site2]
  2038. """
  2039. self.circuit.distance = 10
  2040. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  2041. self.circuit.save()
  2042. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  2043. site2 = Site.objects.create(name='Site 2', slug='site-2')
  2044. circuittermination1 = CircuitTermination.objects.create(
  2045. circuit=self.circuit,
  2046. termination=self.site,
  2047. term_side='A'
  2048. )
  2049. circuittermination2 = CircuitTermination.objects.create(
  2050. circuit=self.circuit,
  2051. termination=site2,
  2052. term_side='Z'
  2053. )
  2054. cable1 = Cable(
  2055. profile=CableProfileChoices.SINGLE_1C1P,
  2056. length=200,
  2057. length_unit=CableLengthUnitChoices.UNIT_METER,
  2058. a_terminations=[interface1],
  2059. b_terminations=[circuittermination1]
  2060. )
  2061. cable1.clean()
  2062. cable1.save()
  2063. # The distance of a circuit crossed to reach its far-side site counts toward the total length
  2064. path = self.assertPathExists(
  2065. (interface1, cable1, circuittermination1, circuittermination2, site2),
  2066. is_active=True
  2067. )
  2068. self.assertEqual(path.get_total_length(), (10200, True))
  2069. def test_230_total_length_via_circuit_to_providernetwork(self):
  2070. """
  2071. [IF1] --C1-- [CT1] [CT2] --> [PN1]
  2072. """
  2073. self.circuit.distance = 10
  2074. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  2075. self.circuit.save()
  2076. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  2077. providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider)
  2078. circuittermination1 = CircuitTermination.objects.create(
  2079. circuit=self.circuit,
  2080. termination=self.site,
  2081. term_side='A'
  2082. )
  2083. circuittermination2 = CircuitTermination.objects.create(
  2084. circuit=self.circuit,
  2085. termination=providernetwork,
  2086. term_side='Z'
  2087. )
  2088. cable1 = Cable(
  2089. profile=CableProfileChoices.SINGLE_1C1P,
  2090. length=200,
  2091. length_unit=CableLengthUnitChoices.UNIT_METER,
  2092. a_terminations=[interface1],
  2093. b_terminations=[circuittermination1]
  2094. )
  2095. cable1.clean()
  2096. cable1.save()
  2097. # The distance of a circuit crossed to reach a provider network counts toward the total length
  2098. path = self.assertPathExists(
  2099. (interface1, cable1, circuittermination1, circuittermination2, providernetwork),
  2100. is_complete=True,
  2101. is_active=True
  2102. )
  2103. self.assertEqual(path.get_total_length(), (10200, True))
  2104. def test_231_total_length_via_parallel_circuits(self):
  2105. """
  2106. [IF1] --C1-- [CT1_A] [CT1_Z] --C2-- [IF2]
  2107. [CT2_A] [CT2_Z]
  2108. """
  2109. self.circuit.distance = 10
  2110. self.circuit.distance_unit = DistanceUnitChoices.UNIT_KILOMETER
  2111. self.circuit.save()
  2112. circuit2 = Circuit.objects.create(
  2113. provider=self.circuit.provider,
  2114. type=self.circuit.type,
  2115. cid='Circuit 2',
  2116. distance=5,
  2117. distance_unit=DistanceUnitChoices.UNIT_KILOMETER
  2118. )
  2119. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  2120. interface2 = Interface.objects.create(device=self.device, name='Interface 2')
  2121. circuittermination1_A = CircuitTermination.objects.create(
  2122. circuit=self.circuit,
  2123. termination=self.site,
  2124. term_side='A'
  2125. )
  2126. circuittermination1_Z = CircuitTermination.objects.create(
  2127. circuit=self.circuit,
  2128. termination=self.site,
  2129. term_side='Z'
  2130. )
  2131. circuittermination2_A = CircuitTermination.objects.create(
  2132. circuit=circuit2,
  2133. termination=self.site,
  2134. term_side='A'
  2135. )
  2136. circuittermination2_Z = CircuitTermination.objects.create(
  2137. circuit=circuit2,
  2138. termination=self.site,
  2139. term_side='Z'
  2140. )
  2141. # Create cables
  2142. cable1 = Cable(
  2143. length=200,
  2144. length_unit=CableLengthUnitChoices.UNIT_METER,
  2145. a_terminations=[interface1],
  2146. b_terminations=[circuittermination1_A, circuittermination2_A]
  2147. )
  2148. cable1.clean()
  2149. cable1.save()
  2150. cable2 = Cable(
  2151. length=200,
  2152. length_unit=CableLengthUnitChoices.UNIT_METER,
  2153. a_terminations=[circuittermination1_Z, circuittermination2_Z],
  2154. b_terminations=[interface2]
  2155. )
  2156. cable2.clean()
  2157. cable2.save()
  2158. # Check for complete paths in both directions
  2159. paths = [
  2160. self.assertPathExists(
  2161. (
  2162. interface1, cable1, (circuittermination1_A, circuittermination2_A),
  2163. (circuittermination1_Z, circuittermination2_Z), cable2, interface2,
  2164. ),
  2165. is_complete=True,
  2166. is_active=True
  2167. ),
  2168. self.assertPathExists(
  2169. (
  2170. interface2, cable2, (circuittermination1_Z, circuittermination2_Z),
  2171. (circuittermination1_A, circuittermination2_A), cable1, interface1,
  2172. ),
  2173. is_complete=True,
  2174. is_active=True
  2175. ),
  2176. ]
  2177. # Parallel circuits crossed in the same hop count only the longest distance
  2178. for path in paths:
  2179. self.assertEqual(path.get_total_length(), (10400, True))
  2180. # An unset distance on one parallel circuit keeps the longest known distance but is non-definitive
  2181. circuit2.distance = None
  2182. circuit2.save()
  2183. for path in paths:
  2184. self.assertEqual(path.get_total_length(), (10400, False))
  2185. def test_304_add_port_mapping_between_connected_ports(self):
  2186. """
  2187. [IF1] --C1-- [FP1] [RP1] --C2-- [IF2]
  2188. """
  2189. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  2190. interface2 = Interface.objects.create(device=self.device, name='Interface 2')
  2191. frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
  2192. rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
  2193. cable1 = Cable(
  2194. a_terminations=[interface1],
  2195. b_terminations=[frontport1]
  2196. )
  2197. cable1.save()
  2198. cable2 = Cable(
  2199. a_terminations=[interface2],
  2200. b_terminations=[rearport1]
  2201. )
  2202. cable2.save()
  2203. # Check for incomplete paths
  2204. self.assertPathExists(
  2205. (interface1, cable1, frontport1),
  2206. is_complete=False,
  2207. is_active=True
  2208. )
  2209. self.assertPathExists(
  2210. (interface2, cable2, rearport1),
  2211. is_complete=False,
  2212. is_active=True
  2213. )
  2214. # Create a PortMapping between frontport1 and rearport1
  2215. PortMapping.objects.create(
  2216. device=self.device,
  2217. front_port=frontport1,
  2218. front_port_position=1,
  2219. rear_port=rearport1,
  2220. rear_port_position=1,
  2221. )
  2222. # Check that paths are now complete
  2223. self.assertPathExists(
  2224. (interface1, cable1, frontport1, rearport1, cable2, interface2),
  2225. is_complete=True,
  2226. is_active=True
  2227. )
  2228. self.assertPathExists(
  2229. (interface2, cable2, rearport1, frontport1, cable1, interface1),
  2230. is_complete=True,
  2231. is_active=True
  2232. )
  2233. def test_305_delete_port_mapping_between_connected_ports(self):
  2234. """
  2235. [IF1] --C1-- [FP1] [RP1] --C2-- [IF2]
  2236. """
  2237. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  2238. interface2 = Interface.objects.create(device=self.device, name='Interface 2')
  2239. frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
  2240. rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
  2241. cable1 = Cable(
  2242. a_terminations=[interface1],
  2243. b_terminations=[frontport1]
  2244. )
  2245. cable1.save()
  2246. cable2 = Cable(
  2247. a_terminations=[interface2],
  2248. b_terminations=[rearport1]
  2249. )
  2250. cable2.save()
  2251. portmapping1 = PortMapping.objects.create(
  2252. device=self.device,
  2253. front_port=frontport1,
  2254. front_port_position=1,
  2255. rear_port=rearport1,
  2256. rear_port_position=1,
  2257. )
  2258. # Check for complete paths
  2259. self.assertPathExists(
  2260. (interface1, cable1, frontport1, rearport1, cable2, interface2),
  2261. is_complete=True,
  2262. is_active=True
  2263. )
  2264. self.assertPathExists(
  2265. (interface2, cable2, rearport1, frontport1, cable1, interface1),
  2266. is_complete=True,
  2267. is_active=True
  2268. )
  2269. # Delete the PortMapping between frontport1 and rearport1
  2270. portmapping1.delete()
  2271. # Check that paths are no longer complete
  2272. self.assertPathExists(
  2273. (interface1, cable1, frontport1),
  2274. is_complete=False,
  2275. is_active=True
  2276. )
  2277. self.assertPathExists(
  2278. (interface2, cable2, rearport1),
  2279. is_complete=False,
  2280. is_active=True
  2281. )
  2282. def test_306_change_port_mapping_between_connected_ports(self):
  2283. """
  2284. [IF1] --C1-- [FP1] [RP1] --C3-- [IF3]
  2285. [IF2] --C2-- [FP2] [RP3] --C4-- [IF4]
  2286. """
  2287. interface1 = Interface.objects.create(device=self.device, name='Interface 1')
  2288. interface2 = Interface.objects.create(device=self.device, name='Interface 2')
  2289. interface3 = Interface.objects.create(device=self.device, name='Interface 3')
  2290. interface4 = Interface.objects.create(device=self.device, name='Interface 4')
  2291. frontport1 = FrontPort.objects.create(device=self.device, name='Front Port 1')
  2292. frontport2 = FrontPort.objects.create(device=self.device, name='Front Port 2')
  2293. rearport1 = RearPort.objects.create(device=self.device, name='Rear Port 1')
  2294. rearport2 = RearPort.objects.create(device=self.device, name='Rear Port 2')
  2295. cable1 = Cable(
  2296. a_terminations=[interface1],
  2297. b_terminations=[frontport1]
  2298. )
  2299. cable1.save()
  2300. cable2 = Cable(
  2301. a_terminations=[interface2],
  2302. b_terminations=[frontport2]
  2303. )
  2304. cable2.save()
  2305. cable3 = Cable(
  2306. a_terminations=[interface3],
  2307. b_terminations=[rearport1]
  2308. )
  2309. cable3.save()
  2310. cable4 = Cable(
  2311. a_terminations=[interface4],
  2312. b_terminations=[rearport2]
  2313. )
  2314. cable4.save()
  2315. portmapping1 = PortMapping.objects.create(
  2316. device=self.device,
  2317. front_port=frontport1,
  2318. front_port_position=1,
  2319. rear_port=rearport1,
  2320. rear_port_position=1,
  2321. )
  2322. # Verify expected initial paths
  2323. self.assertPathExists(
  2324. (interface1, cable1, frontport1, rearport1, cable3, interface3),
  2325. is_complete=True,
  2326. is_active=True
  2327. )
  2328. self.assertPathExists(
  2329. (interface3, cable3, rearport1, frontport1, cable1, interface1),
  2330. is_complete=True,
  2331. is_active=True
  2332. )
  2333. # Delete and replace the PortMapping to connect interface1 to interface4
  2334. portmapping1.delete()
  2335. portmapping2 = PortMapping.objects.create(
  2336. device=self.device,
  2337. front_port=frontport1,
  2338. front_port_position=1,
  2339. rear_port=rearport2,
  2340. rear_port_position=1,
  2341. )
  2342. # Verify expected new paths
  2343. self.assertPathExists(
  2344. (interface1, cable1, frontport1, rearport2, cable4, interface4),
  2345. is_complete=True,
  2346. is_active=True
  2347. )
  2348. self.assertPathExists(
  2349. (interface4, cable4, rearport2, frontport1, cable1, interface1),
  2350. is_complete=True,
  2351. is_active=True
  2352. )
  2353. # Delete and replace the PortMapping to connect interface2 to interface4
  2354. portmapping2.delete()
  2355. PortMapping.objects.create(
  2356. device=self.device,
  2357. front_port=frontport2,
  2358. front_port_position=1,
  2359. rear_port=rearport2,
  2360. rear_port_position=1,
  2361. )
  2362. # Verify expected new paths
  2363. self.assertPathExists(
  2364. (interface2, cable2, frontport2, rearport2, cable4, interface4),
  2365. is_complete=True,
  2366. is_active=True
  2367. )
  2368. self.assertPathExists(
  2369. (interface4, cable4, rearport2, frontport2, cable2, interface2),
  2370. is_complete=True,
  2371. is_active=True
  2372. )
  2373. def test_307_change_cable_profile_rebuilds_paths(self):
  2374. """
  2375. [IF1] --C1-- [IF2]
  2376. Applying a profile to an existing cable rebuilds its paths.
  2377. """
  2378. interfaces = [
  2379. Interface.objects.create(device=self.device, name='Interface 1'),
  2380. Interface.objects.create(device=self.device, name='Interface 2'),
  2381. ]
  2382. # Create cable 1 without a profile
  2383. cable1 = Cable(
  2384. a_terminations=[interfaces[0]],
  2385. b_terminations=[interfaces[1]],
  2386. )
  2387. cable1.clean()
  2388. cable1.save()
  2389. self.assertPathExists(
  2390. (interfaces[0], cable1, interfaces[1]),
  2391. is_complete=True,
  2392. is_active=True
  2393. )
  2394. self.assertPathExists(
  2395. (interfaces[1], cable1, interfaces[0]),
  2396. is_complete=True,
  2397. is_active=True
  2398. )
  2399. # Reload so _terminations_modified starts False, as it does in a bulk edit
  2400. cable1 = Cable.objects.get(pk=cable1.pk)
  2401. cable1.profile = CableProfileChoices.SINGLE_1C1P
  2402. cable1.full_clean()
  2403. cable1.save()
  2404. path1 = self.assertPathExists(
  2405. (interfaces[0], cable1, interfaces[1]),
  2406. is_complete=True,
  2407. is_active=True
  2408. )
  2409. path2 = self.assertPathExists(
  2410. (interfaces[1], cable1, interfaces[0]),
  2411. is_complete=True,
  2412. is_active=True
  2413. )
  2414. self.assertEqual(CablePath.objects.count(), 2)
  2415. interfaces[0].refresh_from_db()
  2416. interfaces[1].refresh_from_db()
  2417. self.assertPathIsSet(interfaces[0], path1)
  2418. self.assertPathIsSet(interfaces[1], path2)
  2419. def test_308_change_cable_profile_regroups_trunk_paths(self):
  2420. """
  2421. [IF1] --C1-- [IF3]
  2422. [IF2] [IF4]
  2423. Applying a trunk profile to an existing cable regroups its paths by connector, and
  2424. clearing it again collapses them.
  2425. """
  2426. interfaces = [
  2427. Interface.objects.create(device=self.device, name='Interface 1'),
  2428. Interface.objects.create(device=self.device, name='Interface 2'),
  2429. Interface.objects.create(device=self.device, name='Interface 3'),
  2430. Interface.objects.create(device=self.device, name='Interface 4'),
  2431. ]
  2432. # Create cable 1 without a profile
  2433. cable1 = Cable(
  2434. a_terminations=[interfaces[0], interfaces[1]],
  2435. b_terminations=[interfaces[2], interfaces[3]],
  2436. )
  2437. cable1.clean()
  2438. cable1.save()
  2439. # Without a profile both terminations on each end share a single path
  2440. self.assertPathExists(
  2441. ((interfaces[0], interfaces[1]), cable1, (interfaces[2], interfaces[3])),
  2442. is_complete=True,
  2443. is_active=True
  2444. )
  2445. self.assertPathExists(
  2446. ((interfaces[2], interfaces[3]), cable1, (interfaces[0], interfaces[1])),
  2447. is_complete=True,
  2448. is_active=True
  2449. )
  2450. self.assertEqual(CablePath.objects.count(), 2)
  2451. # Reload so _terminations_modified starts False, as it does in a bulk edit
  2452. cable1 = Cable.objects.get(pk=cable1.pk)
  2453. cable1.profile = CableProfileChoices.TRUNK_2C1P
  2454. cable1.full_clean()
  2455. cable1.save()
  2456. path1 = self.assertPathExists(
  2457. (interfaces[0], cable1, interfaces[2]),
  2458. is_complete=True,
  2459. is_active=True
  2460. )
  2461. path2 = self.assertPathExists(
  2462. (interfaces[1], cable1, interfaces[3]),
  2463. is_complete=True,
  2464. is_active=True
  2465. )
  2466. path3 = self.assertPathExists(
  2467. (interfaces[2], cable1, interfaces[0]),
  2468. is_complete=True,
  2469. is_active=True
  2470. )
  2471. path4 = self.assertPathExists(
  2472. (interfaces[3], cable1, interfaces[1]),
  2473. is_complete=True,
  2474. is_active=True
  2475. )
  2476. self.assertEqual(CablePath.objects.count(), 4)
  2477. for interface in interfaces:
  2478. interface.refresh_from_db()
  2479. self.assertPathIsSet(interfaces[0], path1)
  2480. self.assertPathIsSet(interfaces[1], path2)
  2481. self.assertPathIsSet(interfaces[2], path3)
  2482. self.assertPathIsSet(interfaces[3], path4)
  2483. self.assertEqual(interfaces[0].cable_connector, 1)
  2484. self.assertEqual(interfaces[1].cable_connector, 2)
  2485. self.assertEqual(interfaces[2].cable_connector, 1)
  2486. self.assertEqual(interfaces[3].cable_connector, 2)
  2487. for interface in interfaces:
  2488. self.assertEqual(interface.cable_positions, [1])
  2489. # Clearing the profile is a bulk-edit action in its own right
  2490. cable1 = Cable.objects.get(pk=cable1.pk)
  2491. cable1.profile = ''
  2492. cable1.full_clean()
  2493. cable1.save()
  2494. path5 = self.assertPathExists(
  2495. ((interfaces[0], interfaces[1]), cable1, (interfaces[2], interfaces[3])),
  2496. is_complete=True,
  2497. is_active=True
  2498. )
  2499. path6 = self.assertPathExists(
  2500. ((interfaces[2], interfaces[3]), cable1, (interfaces[0], interfaces[1])),
  2501. is_complete=True,
  2502. is_active=True
  2503. )
  2504. self.assertEqual(CablePath.objects.count(), 2)
  2505. for interface in interfaces:
  2506. interface.refresh_from_db()
  2507. self.assertIsNone(interface.cable_connector)
  2508. self.assertPathIsSet(interfaces[0], path5)
  2509. self.assertPathIsSet(interfaces[1], path5)
  2510. self.assertPathIsSet(interfaces[2], path6)
  2511. self.assertPathIsSet(interfaces[3], path6)
  2512. def test_309_change_midspan_cable_profile_rebuilds_paths(self):
  2513. """
  2514. [IF1] --C1-- [FP1][RP1] --C3-- [RP2][FP2] --C2-- [IF2]
  2515. Applying a profile to a cable which terminates on pass-through ports rebuilds the
  2516. paths traversing it. The rear ports are not path origins, so a missing rebuild
  2517. truncates those paths rather than deleting them.
  2518. """
  2519. interfaces = [
  2520. Interface.objects.create(device=self.device, name='Interface 1'),
  2521. Interface.objects.create(device=self.device, name='Interface 2'),
  2522. ]
  2523. rear_ports = [
  2524. RearPort.objects.create(device=self.device, name='Rear Port 1'),
  2525. RearPort.objects.create(device=self.device, name='Rear Port 2'),
  2526. ]
  2527. front_ports = [
  2528. FrontPort.objects.create(device=self.device, name='Front Port 1'),
  2529. FrontPort.objects.create(device=self.device, name='Front Port 2'),
  2530. ]
  2531. for front_port, rear_port in zip(front_ports, rear_ports):
  2532. PortMapping.objects.create(
  2533. device=self.device,
  2534. front_port=front_port,
  2535. front_port_position=1,
  2536. rear_port=rear_port,
  2537. rear_port_position=1
  2538. )
  2539. cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[front_ports[0]])
  2540. cable1.clean()
  2541. cable1.save()
  2542. cable2 = Cable(a_terminations=[front_ports[1]], b_terminations=[interfaces[1]])
  2543. cable2.clean()
  2544. cable2.save()
  2545. # Create the mid-span cable without a profile
  2546. cable3 = Cable(a_terminations=[rear_ports[0]], b_terminations=[rear_ports[1]])
  2547. cable3.clean()
  2548. cable3.save()
  2549. nodes_a_to_b = (
  2550. interfaces[0], cable1, front_ports[0], rear_ports[0], cable3, rear_ports[1], front_ports[1], cable2,
  2551. interfaces[1],
  2552. )
  2553. nodes_b_to_a = (
  2554. interfaces[1], cable2, front_ports[1], rear_ports[1], cable3, rear_ports[0], front_ports[0], cable1,
  2555. interfaces[0],
  2556. )
  2557. self.assertPathExists(nodes_a_to_b, is_complete=True, is_active=True)
  2558. self.assertPathExists(nodes_b_to_a, is_complete=True, is_active=True)
  2559. self.assertEqual(CablePath.objects.count(), 2)
  2560. # Reload so _terminations_modified starts False, as it does in a bulk edit
  2561. cable3 = Cable.objects.get(pk=cable3.pk)
  2562. cable3.profile = CableProfileChoices.SINGLE_1C1P
  2563. cable3.full_clean()
  2564. cable3.save()
  2565. path1 = self.assertPathExists(nodes_a_to_b, is_complete=True, is_active=True)
  2566. path2 = self.assertPathExists(nodes_b_to_a, is_complete=True, is_active=True)
  2567. self.assertEqual(CablePath.objects.count(), 2)
  2568. for interface in interfaces:
  2569. interface.refresh_from_db()
  2570. self.assertPathIsSet(interfaces[0], path1)
  2571. self.assertPathIsSet(interfaces[1], path2)
  2572. def test_310_repeat_save_does_not_recreate_paths(self):
  2573. """
  2574. [IF1] --C1-- [IF2]
  2575. Saving an unchanged cable again leaves its terminations and paths untouched.
  2576. """
  2577. interfaces = [
  2578. Interface.objects.create(device=self.device, name='Interface 1'),
  2579. Interface.objects.create(device=self.device, name='Interface 2'),
  2580. ]
  2581. cable1 = Cable(
  2582. a_terminations=[interfaces[0]],
  2583. b_terminations=[interfaces[1]],
  2584. )
  2585. cable1.clean()
  2586. cable1.save()
  2587. path_pks = set(CablePath.objects.values_list('pk', flat=True))
  2588. termination_pks = set(CableTermination.objects.filter(cable=cable1).values_list('pk', flat=True))
  2589. self.assertEqual(len(path_pks), 2)
  2590. self.assertEqual(len(termination_pks), 2)
  2591. # Saving the same instance again must not duplicate its paths
  2592. cable1.save()
  2593. self.assertEqual(set(CablePath.objects.values_list('pk', flat=True)), path_pks)
  2594. self.assertEqual(
  2595. set(CableTermination.objects.filter(cable=cable1).values_list('pk', flat=True)),
  2596. termination_pks
  2597. )
  2598. # Reload so _terminations_modified starts False, as it does in a bulk edit
  2599. cable1 = Cable.objects.get(pk=cable1.pk)
  2600. cable1.profile = CableProfileChoices.SINGLE_1C1P
  2601. cable1.full_clean()
  2602. cable1.save()
  2603. path_pks = set(CablePath.objects.values_list('pk', flat=True))
  2604. termination_pks = set(CableTermination.objects.filter(cable=cable1).values_list('pk', flat=True))
  2605. self.assertEqual(len(path_pks), 2)
  2606. self.assertEqual(len(termination_pks), 2)
  2607. # The profile change is applied once, so a second save must not recreate anything
  2608. cable1.save()
  2609. self.assertEqual(set(CablePath.objects.values_list('pk', flat=True)), path_pks)
  2610. self.assertEqual(
  2611. set(CableTermination.objects.filter(cable=cable1).values_list('pk', flat=True)),
  2612. termination_pks
  2613. )
  2614. def test_311_moving_a_midspan_termination_preserves_far_end_paths(self):
  2615. """
  2616. [IF1] --C1-- [FP1][RP1] --C3-- [RP2][FP2] --C2-- [IF2]
  2617. becomes
  2618. [IF1] --C1-- [FP1][RP1] --C3-- [RP3][FP3] --C4-- [IF3]
  2619. Rear ports originate no path, so moving a mid-span cable's end must retrace the rows which traverse it
  2620. rather than delete them: their origins cannot be recovered from the cable's own terminations.
  2621. """
  2622. interfaces = [
  2623. Interface.objects.create(device=self.device, name=f'Interface {i}') for i in range(1, 4)
  2624. ]
  2625. rear_ports = [
  2626. RearPort.objects.create(device=self.device, name=f'Rear Port {i}') for i in range(1, 4)
  2627. ]
  2628. front_ports = [
  2629. FrontPort.objects.create(device=self.device, name=f'Front Port {i}') for i in range(1, 4)
  2630. ]
  2631. for front_port, rear_port in zip(front_ports, rear_ports):
  2632. PortMapping.objects.create(
  2633. device=self.device,
  2634. front_port=front_port,
  2635. front_port_position=1,
  2636. rear_port=rear_port,
  2637. rear_port_position=1
  2638. )
  2639. cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[front_ports[0]])
  2640. cable1.clean()
  2641. cable1.save()
  2642. cable2 = Cable(a_terminations=[front_ports[1]], b_terminations=[interfaces[1]])
  2643. cable2.clean()
  2644. cable2.save()
  2645. cable3 = Cable(a_terminations=[rear_ports[0]], b_terminations=[rear_ports[1]])
  2646. cable3.clean()
  2647. cable3.save()
  2648. cable4 = Cable(a_terminations=[front_ports[2]], b_terminations=[interfaces[2]])
  2649. cable4.clean()
  2650. cable4.save()
  2651. before = (
  2652. interfaces[0], cable1, front_ports[0], rear_ports[0], cable3, rear_ports[1], front_ports[1], cable2,
  2653. interfaces[1],
  2654. )
  2655. self.assertCurrentPathExists(before, is_complete=True, is_active=True)
  2656. self.assertCurrentPathExists(tuple(reversed(before)), is_complete=True, is_active=True)
  2657. # Two for the completed link, one for the third interface stopping at its own rear port
  2658. self.assertEqual(CablePath.objects.count(), 3)
  2659. cable3 = Cable.objects.get(pk=cable3.pk)
  2660. cable3.b_terminations = [rear_ports[2]]
  2661. cable3.full_clean()
  2662. cable3.save()
  2663. after = (
  2664. interfaces[0], cable1, front_ports[0], rear_ports[0], cable3, rear_ports[2], front_ports[2], cable4,
  2665. interfaces[2],
  2666. )
  2667. self.assertCurrentPathExists(after, is_complete=True, is_active=True)
  2668. self.assertCurrentPathExists(tuple(reversed(after)), is_complete=True, is_active=True)
  2669. self.assertCurrentPathExists(
  2670. (interfaces[1], cable2, front_ports[1], rear_ports[1]), is_complete=False
  2671. )
  2672. self.assertEqual(CablePath.objects.count(), 3)