test_cablepaths2.py 97 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514
  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 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. )