test_api.py 138 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641
  1. import json
  2. from django.test import override_settings, tag
  3. from django.urls import reverse
  4. from django.utils.translation import gettext as _
  5. from rest_framework import status
  6. from core.models import ObjectType
  7. from dcim.choices import *
  8. from dcim.constants import *
  9. from dcim.models import *
  10. from extras.models import ConfigTemplate, Tag
  11. from ipam.choices import VLANQinQRoleChoices
  12. from ipam.models import ASN, RIR, VLAN, VRF
  13. from netbox.api.serializers import GenericObjectSerializer
  14. from tenancy.models import Tenant
  15. from users.constants import TOKEN_PREFIX
  16. from users.models import ObjectPermission, Token, User
  17. from utilities.testing import APITestCase, APIViewTestCases, create_test_device, disable_logging
  18. from virtualization.models import Cluster, ClusterType
  19. from wireless.choices import WirelessChannelChoices
  20. from wireless.models import WirelessLAN
  21. class AppTest(APITestCase):
  22. def test_root(self):
  23. url = reverse('dcim-api:api-root')
  24. response = self.client.get('{}?format=api'.format(url), **self.header)
  25. self.assertEqual(response.status_code, 200)
  26. class Mixins:
  27. class ComponentTraceMixin(APITestCase):
  28. peer_termination_type = None
  29. def test_trace(self):
  30. """
  31. Test tracing a device component's attached cable.
  32. """
  33. obj = self.model.objects.first()
  34. peer_device = Device.objects.create(
  35. site=Site.objects.first(),
  36. device_type=DeviceType.objects.first(),
  37. role=DeviceRole.objects.first(),
  38. name='Peer Device'
  39. )
  40. if self.peer_termination_type is None:
  41. raise NotImplementedError(_("Test case must set peer_termination_type"))
  42. peer_obj = self.peer_termination_type.objects.create(
  43. device=peer_device,
  44. name='Peer Termination'
  45. )
  46. cable = Cable(a_terminations=[obj], b_terminations=[peer_obj], label='Cable 1')
  47. cable.save()
  48. self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
  49. url = reverse(f'dcim-api:{self.model._meta.model_name}-trace', kwargs={'pk': obj.pk})
  50. response = self.client.get(url, **self.header)
  51. self.assertHttpStatus(response, status.HTTP_200_OK)
  52. self.assertEqual(len(response.data), 1)
  53. segment1 = response.data[0]
  54. self.assertEqual(segment1[0][0]['name'], obj.name)
  55. self.assertEqual(segment1[1]['label'], cable.label)
  56. self.assertEqual(segment1[2][0]['name'], peer_obj.name)
  57. class RegionTest(APIViewTestCases.APIViewTestCase):
  58. model = Region
  59. brief_fields = ['_depth', 'description', 'display', 'id', 'name', 'site_count', 'slug', 'url']
  60. create_data = [
  61. {
  62. 'name': 'Region 4',
  63. 'slug': 'region-4',
  64. 'comments': 'this is region 4, not region 5',
  65. },
  66. {
  67. 'name': 'Region 5',
  68. 'slug': 'region-5',
  69. },
  70. {
  71. 'name': 'Region 6',
  72. 'slug': 'region-6',
  73. },
  74. ]
  75. bulk_update_data = {
  76. 'description': 'New description',
  77. 'comments': 'New comments',
  78. }
  79. @classmethod
  80. def setUpTestData(cls):
  81. Region.objects.create(name='Region 1', slug='region-1')
  82. Region.objects.create(name='Region 2', slug='region-2', comments='what in the world is happening?')
  83. Region.objects.create(name='Region 3', slug='region-3')
  84. class SiteGroupTest(APIViewTestCases.APIViewTestCase):
  85. model = SiteGroup
  86. brief_fields = ['_depth', 'description', 'display', 'id', 'name', 'site_count', 'slug', 'url']
  87. create_data = [
  88. {
  89. 'name': 'Site Group 4',
  90. 'slug': 'site-group-4',
  91. 'comments': '',
  92. },
  93. {
  94. 'name': 'Site Group 5',
  95. 'slug': 'site-group-5',
  96. 'comments': 'not actually empty',
  97. },
  98. {
  99. 'name': 'Site Group 6',
  100. 'slug': 'site-group-6',
  101. 'comments': 'Do I really exist?',
  102. },
  103. ]
  104. bulk_update_data = {
  105. 'description': 'New description',
  106. 'comments': 'I do exist!',
  107. }
  108. @classmethod
  109. def setUpTestData(cls):
  110. SiteGroup.objects.create(name='Site Group 1', slug='site-group-1')
  111. SiteGroup.objects.create(name='Site Group 2', slug='site-group-2', comments='')
  112. SiteGroup.objects.create(name='Site Group 3', slug='site-group-3', comments='Hi!')
  113. class SiteTest(APIViewTestCases.APIViewTestCase):
  114. model = Site
  115. brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url']
  116. bulk_update_data = {
  117. 'status': 'planned',
  118. }
  119. @classmethod
  120. def setUpTestData(cls):
  121. regions = (
  122. Region.objects.create(name='Region 1', slug='region-1'),
  123. Region.objects.create(name='Region 2', slug='region-2'),
  124. )
  125. groups = (
  126. SiteGroup.objects.create(name='Site Group 1', slug='site-group-1'),
  127. SiteGroup.objects.create(name='Site Group 2', slug='site-group-2'),
  128. )
  129. sites = (
  130. Site(region=regions[0], group=groups[0], name='Site 1', slug='site-1'),
  131. Site(region=regions[0], group=groups[0], name='Site 2', slug='site-2'),
  132. Site(region=regions[0], group=groups[0], name='Site 3', slug='site-3'),
  133. )
  134. Site.objects.bulk_create(sites)
  135. rir = RIR.objects.create(name='RFC 6996', is_private=True)
  136. tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
  137. asns = [
  138. ASN(asn=65000 + i, rir=rir) for i in range(8)
  139. ]
  140. ASN.objects.bulk_create(asns)
  141. cls.create_data = [
  142. {
  143. 'name': 'Site 4',
  144. 'slug': 'site-4',
  145. 'region': regions[1].pk,
  146. 'group': groups[1].pk,
  147. 'status': SiteStatusChoices.STATUS_ACTIVE,
  148. 'asns': [asns[0].pk, asns[1].pk],
  149. 'tenant': tenant.pk,
  150. },
  151. {
  152. 'name': 'Site 5',
  153. 'slug': 'site-5',
  154. 'region': regions[1].pk,
  155. 'group': groups[1].pk,
  156. 'status': SiteStatusChoices.STATUS_ACTIVE,
  157. 'asns': [asns[2].pk, asns[3].pk],
  158. },
  159. {
  160. 'name': 'Site 6',
  161. 'slug': 'site-6',
  162. 'region': regions[1].pk,
  163. 'group': groups[1].pk,
  164. 'status': SiteStatusChoices.STATUS_ACTIVE,
  165. 'asns': [asns[4].pk, asns[5].pk],
  166. },
  167. ]
  168. def test_add_tags(self):
  169. """
  170. Add tags to an existing object via the add_tags field.
  171. """
  172. site = Site.objects.first()
  173. tags = Tag.objects.bulk_create((
  174. Tag(name='Alpha', slug='alpha'),
  175. Tag(name='Bravo', slug='bravo'),
  176. Tag(name='Charlie', slug='charlie'),
  177. ))
  178. site.tags.set([tags[0], tags[1]])
  179. # Grant change permission
  180. obj_perm = ObjectPermission(name='Test permission', actions=['change'])
  181. obj_perm.save()
  182. obj_perm.users.add(self.user)
  183. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  184. url = self._get_detail_url(site)
  185. data = {
  186. 'add_tags': [{'name': 'Charlie'}],
  187. }
  188. response = self.client.patch(url, data, format='json', **self.header)
  189. self.assertHttpStatus(response, status.HTTP_200_OK)
  190. # Verify all three tags are now assigned
  191. tag_names = sorted(site.tags.values_list('name', flat=True))
  192. self.assertEqual(tag_names, ['Alpha', 'Bravo', 'Charlie'])
  193. # Verify add_tags and remove_tags are not in the response
  194. self.assertNotIn('add_tags', response.data)
  195. self.assertNotIn('remove_tags', response.data)
  196. self.assertIn('tags', response.data)
  197. def test_remove_tags(self):
  198. """
  199. Remove tags from an existing object via the remove_tags field.
  200. """
  201. site = Site.objects.first()
  202. tags = Tag.objects.bulk_create((
  203. Tag(name='Alpha', slug='alpha'),
  204. Tag(name='Bravo', slug='bravo'),
  205. Tag(name='Charlie', slug='charlie'),
  206. ))
  207. site.tags.set(tags)
  208. # Grant change permission
  209. obj_perm = ObjectPermission(name='Test permission', actions=['change'])
  210. obj_perm.save()
  211. obj_perm.users.add(self.user)
  212. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  213. url = self._get_detail_url(site)
  214. data = {
  215. 'remove_tags': [{'name': 'Charlie'}],
  216. }
  217. response = self.client.patch(url, data, format='json', **self.header)
  218. self.assertHttpStatus(response, status.HTTP_200_OK)
  219. # Verify only Alpha and Bravo remain
  220. tag_names = sorted(site.tags.values_list('name', flat=True))
  221. self.assertEqual(tag_names, ['Alpha', 'Bravo'])
  222. def test_remove_tags_not_assigned(self):
  223. """
  224. Removing a tag that is not assigned should not raise an error.
  225. """
  226. site = Site.objects.first()
  227. tags = Tag.objects.bulk_create((
  228. Tag(name='Alpha', slug='alpha'),
  229. Tag(name='Bravo', slug='bravo'),
  230. Tag(name='Charlie', slug='charlie'),
  231. ))
  232. site.tags.set([tags[0], tags[1]])
  233. # Grant change permission
  234. obj_perm = ObjectPermission(name='Test permission', actions=['change'])
  235. obj_perm.save()
  236. obj_perm.users.add(self.user)
  237. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  238. url = self._get_detail_url(site)
  239. data = {
  240. 'remove_tags': [{'name': 'Charlie'}],
  241. }
  242. response = self.client.patch(url, data, format='json', **self.header)
  243. self.assertHttpStatus(response, status.HTTP_200_OK)
  244. # Tags should be unchanged
  245. tag_names = sorted(site.tags.values_list('name', flat=True))
  246. self.assertEqual(tag_names, ['Alpha', 'Bravo'])
  247. def test_add_and_remove_tags(self):
  248. """
  249. Add and remove tags in the same request.
  250. """
  251. site = Site.objects.first()
  252. tags = Tag.objects.bulk_create((
  253. Tag(name='Alpha', slug='alpha'),
  254. Tag(name='Bravo', slug='bravo'),
  255. Tag(name='Charlie', slug='charlie'),
  256. ))
  257. site.tags.set([tags[0], tags[1]])
  258. # Grant change permission
  259. obj_perm = ObjectPermission(name='Test permission', actions=['change'])
  260. obj_perm.save()
  261. obj_perm.users.add(self.user)
  262. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  263. url = self._get_detail_url(site)
  264. data = {
  265. 'add_tags': [{'name': 'Charlie'}],
  266. 'remove_tags': [{'name': 'Alpha'}],
  267. }
  268. response = self.client.patch(url, data, format='json', **self.header)
  269. self.assertHttpStatus(response, status.HTTP_200_OK)
  270. # Verify Bravo and Charlie remain
  271. tag_names = sorted(site.tags.values_list('name', flat=True))
  272. self.assertEqual(tag_names, ['Bravo', 'Charlie'])
  273. def test_tags_with_add_tags_error(self):
  274. """
  275. Specifying tags together with add_tags or remove_tags should raise a validation error.
  276. """
  277. site = Site.objects.first()
  278. Tag.objects.bulk_create((
  279. Tag(name='Alpha', slug='alpha'),
  280. Tag(name='Bravo', slug='bravo'),
  281. ))
  282. # Grant change permission
  283. obj_perm = ObjectPermission(name='Test permission', actions=['change'])
  284. obj_perm.save()
  285. obj_perm.users.add(self.user)
  286. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  287. url = self._get_detail_url(site)
  288. data = {
  289. 'tags': [{'name': 'Alpha'}],
  290. 'add_tags': [{'name': 'Bravo'}],
  291. }
  292. response = self.client.patch(url, data, format='json', **self.header)
  293. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  294. def test_create_with_add_tags(self):
  295. """
  296. Create a new object using add_tags.
  297. """
  298. Tag.objects.bulk_create((
  299. Tag(name='Alpha', slug='alpha'),
  300. Tag(name='Bravo', slug='bravo'),
  301. ))
  302. obj_perm = ObjectPermission(name='Test permission', actions=['add'])
  303. obj_perm.save()
  304. obj_perm.users.add(self.user)
  305. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  306. data = {
  307. 'name': 'Site 10',
  308. 'slug': 'site-10',
  309. 'add_tags': [{'name': 'Alpha'}, {'name': 'Bravo'}],
  310. }
  311. response = self.client.post(self._get_list_url(), data, format='json', **self.header)
  312. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  313. site = Site.objects.get(pk=response.data['id'])
  314. tag_names = sorted(site.tags.values_list('name', flat=True))
  315. self.assertEqual(tag_names, ['Alpha', 'Bravo'])
  316. def test_create_with_remove_tags_error(self):
  317. """
  318. Using remove_tags when creating a new object should raise a validation error.
  319. """
  320. Tag.objects.bulk_create((
  321. Tag(name='Alpha', slug='alpha'),
  322. ))
  323. obj_perm = ObjectPermission(name='Test permission', actions=['add'])
  324. obj_perm.save()
  325. obj_perm.users.add(self.user)
  326. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  327. data = {
  328. 'name': 'Site 10',
  329. 'slug': 'site-10',
  330. 'remove_tags': [{'name': 'Alpha'}],
  331. }
  332. response = self.client.post(self._get_list_url(), data, format='json', **self.header)
  333. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  334. def test_add_and_remove_same_tag_error(self):
  335. """
  336. Including the same tag in both add_tags and remove_tags should raise a validation error.
  337. """
  338. site = Site.objects.first()
  339. Tag.objects.bulk_create((
  340. Tag(name='Alpha', slug='alpha'),
  341. Tag(name='Bravo', slug='bravo'),
  342. ))
  343. obj_perm = ObjectPermission(name='Test permission', actions=['change'])
  344. obj_perm.save()
  345. obj_perm.users.add(self.user)
  346. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  347. url = self._get_detail_url(site)
  348. data = {
  349. 'add_tags': [{'name': 'Alpha'}, {'name': 'Bravo'}],
  350. 'remove_tags': [{'name': 'Alpha'}],
  351. }
  352. response = self.client.patch(url, data, format='json', **self.header)
  353. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  354. class LocationTest(APIViewTestCases.APIViewTestCase):
  355. model = Location
  356. brief_fields = ['_depth', 'description', 'display', 'id', 'name', 'rack_count', 'slug', 'url']
  357. bulk_update_data = {
  358. 'description': 'New description',
  359. }
  360. user_permissions = ('dcim.view_site',)
  361. @classmethod
  362. def setUpTestData(cls):
  363. sites = (
  364. Site(name='Site 1', slug='site-1'),
  365. Site(name='Site 2', slug='site-2'),
  366. )
  367. Site.objects.bulk_create(sites)
  368. parent_locations = (
  369. Location.objects.create(
  370. site=sites[0],
  371. name='Parent Location 1',
  372. slug='parent-location-1',
  373. status=LocationStatusChoices.STATUS_ACTIVE,
  374. comments='First!'
  375. ),
  376. Location.objects.create(
  377. site=sites[1],
  378. name='Parent Location 2',
  379. slug='parent-location-2',
  380. status=LocationStatusChoices.STATUS_ACTIVE,
  381. comments='Second!'
  382. ),
  383. )
  384. Location.objects.create(
  385. site=sites[0],
  386. name='Location 1',
  387. slug='location-1',
  388. parent=parent_locations[0],
  389. status=LocationStatusChoices.STATUS_ACTIVE,
  390. comments='Third!'
  391. )
  392. Location.objects.create(
  393. site=sites[0],
  394. name='Location 2',
  395. slug='location-2',
  396. parent=parent_locations[0],
  397. status=LocationStatusChoices.STATUS_ACTIVE,
  398. )
  399. Location.objects.create(
  400. site=sites[0],
  401. name='Location 3',
  402. slug='location-3',
  403. parent=parent_locations[0],
  404. status=LocationStatusChoices.STATUS_ACTIVE,
  405. )
  406. cls.create_data = [
  407. {
  408. 'name': 'Test Location 4',
  409. 'slug': 'test-location-4',
  410. 'site': sites[1].pk,
  411. 'parent': parent_locations[1].pk,
  412. 'status': LocationStatusChoices.STATUS_PLANNED,
  413. 'comments': '',
  414. },
  415. {
  416. 'name': 'Test Location 5',
  417. 'slug': 'test-location-5',
  418. 'site': sites[1].pk,
  419. 'parent': parent_locations[1].pk,
  420. 'status': LocationStatusChoices.STATUS_PLANNED,
  421. 'comments': 'Somebody should check on this location',
  422. },
  423. {
  424. 'name': 'Test Location 6',
  425. 'slug': 'test-location-6',
  426. 'site': sites[1].pk,
  427. # Omit parent to test uniqueness constraint
  428. 'status': LocationStatusChoices.STATUS_PLANNED,
  429. },
  430. ]
  431. class RackGroupTest(APIViewTestCases.APIViewTestCase):
  432. model = RackGroup
  433. brief_fields = ['description', 'display', 'id', 'name', 'rack_count', 'slug', 'url']
  434. create_data = [
  435. {
  436. 'name': 'Rack Group 4',
  437. 'slug': 'rack-group-4',
  438. },
  439. {
  440. 'name': 'Rack Group 5',
  441. 'slug': 'rack-group-5',
  442. },
  443. {
  444. 'name': 'Rack Group 6',
  445. 'slug': 'rack-group-6',
  446. },
  447. ]
  448. bulk_update_data = {
  449. 'description': 'New description',
  450. }
  451. @classmethod
  452. def setUpTestData(cls):
  453. rack_groups = (
  454. RackGroup(name='Rack Group 1', slug='rack-group-1'),
  455. RackGroup(name='Rack Group 2', slug='rack-group-2'),
  456. RackGroup(name='Rack Group 3', slug='rack-group-3'),
  457. )
  458. RackGroup.objects.bulk_create(rack_groups)
  459. class RackRoleTest(APIViewTestCases.APIViewTestCase):
  460. model = RackRole
  461. brief_fields = ['description', 'display', 'id', 'name', 'rack_count', 'slug', 'url']
  462. create_data = [
  463. {
  464. 'name': 'Rack Role 4',
  465. 'slug': 'rack-role-4',
  466. 'color': 'ffff00',
  467. },
  468. {
  469. 'name': 'Rack Role 5',
  470. 'slug': 'rack-role-5',
  471. 'color': 'ffff00',
  472. },
  473. {
  474. 'name': 'Rack Role 6',
  475. 'slug': 'rack-role-6',
  476. 'color': 'ffff00',
  477. },
  478. ]
  479. bulk_update_data = {
  480. 'description': 'New description',
  481. }
  482. @classmethod
  483. def setUpTestData(cls):
  484. rack_roles = (
  485. RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
  486. RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
  487. RackRole(name='Rack Role 3', slug='rack-role-3', color='0000ff'),
  488. )
  489. RackRole.objects.bulk_create(rack_roles)
  490. class RackTypeTest(APIViewTestCases.APIViewTestCase):
  491. model = RackType
  492. brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'rack_count', 'slug', 'url']
  493. bulk_update_data = {
  494. 'description': 'new description',
  495. }
  496. user_permissions = ('dcim.view_manufacturer',)
  497. @classmethod
  498. def setUpTestData(cls):
  499. manufacturers = (
  500. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  501. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  502. )
  503. Manufacturer.objects.bulk_create(manufacturers)
  504. rack_types = (
  505. RackType(
  506. manufacturer=manufacturers[0],
  507. model='Rack Type 1',
  508. slug='rack-type-1',
  509. form_factor=RackFormFactorChoices.TYPE_CABINET,
  510. ),
  511. RackType(
  512. manufacturer=manufacturers[0],
  513. model='Rack Type 2',
  514. slug='rack-type-2',
  515. form_factor=RackFormFactorChoices.TYPE_CABINET,
  516. ),
  517. RackType(
  518. manufacturer=manufacturers[0],
  519. model='Rack Type 3',
  520. slug='rack-type-3',
  521. form_factor=RackFormFactorChoices.TYPE_CABINET,
  522. ),
  523. )
  524. RackType.objects.bulk_create(rack_types)
  525. cls.create_data = [
  526. {
  527. 'manufacturer': manufacturers[1].pk,
  528. 'model': 'Rack Type 4',
  529. 'slug': 'rack-type-4',
  530. 'form_factor': RackFormFactorChoices.TYPE_CABINET,
  531. },
  532. {
  533. 'manufacturer': manufacturers[1].pk,
  534. 'model': 'Rack Type 5',
  535. 'slug': 'rack-type-5',
  536. 'form_factor': RackFormFactorChoices.TYPE_CABINET,
  537. },
  538. {
  539. 'manufacturer': manufacturers[1].pk,
  540. 'model': 'Rack Type 6',
  541. 'slug': 'rack-type-6',
  542. 'form_factor': RackFormFactorChoices.TYPE_CABINET,
  543. },
  544. ]
  545. class RackTest(APIViewTestCases.APIViewTestCase):
  546. model = Rack
  547. brief_fields = ['description', 'device_count', 'display', 'id', 'name', 'url']
  548. bulk_update_data = {
  549. 'status': 'planned',
  550. }
  551. user_permissions = ('dcim.view_site', )
  552. @classmethod
  553. def setUpTestData(cls):
  554. sites = (
  555. Site(name='Site 1', slug='site-1'),
  556. Site(name='Site 2', slug='site-2'),
  557. )
  558. Site.objects.bulk_create(sites)
  559. locations = (
  560. Location.objects.create(site=sites[0], name='Location 1', slug='location-1'),
  561. Location.objects.create(site=sites[1], name='Location 2', slug='location-2'),
  562. )
  563. rack_groups = (
  564. RackGroup(name='Rack Group 1', slug='rack-group-1'),
  565. RackGroup(name='Rack Group 2', slug='rack-group-2'),
  566. )
  567. RackGroup.objects.bulk_create(rack_groups)
  568. rack_roles = (
  569. RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
  570. RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
  571. )
  572. RackRole.objects.bulk_create(rack_roles)
  573. racks = (
  574. Rack(site=sites[0], location=locations[0], group=rack_groups[0], role=rack_roles[0], name='Rack 1'),
  575. Rack(site=sites[0], location=locations[0], group=rack_groups[0], role=rack_roles[0], name='Rack 2'),
  576. Rack(site=sites[0], location=locations[0], group=rack_groups[0], role=rack_roles[0], name='Rack 3'),
  577. )
  578. Rack.objects.bulk_create(racks)
  579. cls.create_data = [
  580. {
  581. 'name': 'Test Rack 4',
  582. 'site': sites[1].pk,
  583. 'location': locations[1].pk,
  584. 'group': rack_groups[1].pk,
  585. 'role': rack_roles[1].pk,
  586. },
  587. {
  588. 'name': 'Test Rack 5',
  589. 'site': sites[1].pk,
  590. 'location': locations[1].pk,
  591. 'group': rack_groups[1].pk,
  592. 'role': rack_roles[1].pk,
  593. },
  594. {
  595. 'name': 'Test Rack 6',
  596. 'site': sites[1].pk,
  597. 'location': locations[1].pk,
  598. 'group': rack_groups[1].pk,
  599. 'role': rack_roles[1].pk,
  600. },
  601. ]
  602. def test_get_rack_elevation(self):
  603. """
  604. GET a single rack elevation.
  605. """
  606. rack = Rack.objects.first()
  607. self.add_permissions('dcim.view_rack')
  608. url = reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk})
  609. # Retrieve all units
  610. response = self.client.get(url, **self.header)
  611. self.assertEqual(response.data['count'], 84)
  612. # Search for specific units
  613. response = self.client.get(f'{url}?q=3', **self.header)
  614. self.assertEqual(response.data['count'], 26)
  615. response = self.client.get(f'{url}?q=U3', **self.header)
  616. self.assertEqual(response.data['count'], 22)
  617. response = self.client.get(f'{url}?q=U10', **self.header)
  618. self.assertEqual(response.data['count'], 2)
  619. def test_get_rack_elevation_svg(self):
  620. """
  621. GET a single rack elevation in SVG format.
  622. """
  623. rack = Rack.objects.first()
  624. self.add_permissions('dcim.view_rack')
  625. url = '{}?render=svg'.format(reverse('dcim-api:rack-elevation', kwargs={'pk': rack.pk}))
  626. response = self.client.get(url, **self.header)
  627. self.assertHttpStatus(response, status.HTTP_200_OK)
  628. self.assertEqual(response.get('Content-Type'), 'image/svg+xml')
  629. class RackReservationTest(APIViewTestCases.APIViewTestCase):
  630. model = RackReservation
  631. brief_fields = ['description', 'display', 'id', 'status', 'units', 'url', 'user']
  632. bulk_update_data = {
  633. 'description': 'New description',
  634. }
  635. user_permissions = ('dcim.view_rack', 'users.view_user')
  636. @classmethod
  637. def setUpTestData(cls):
  638. user = User.objects.create(username='user1', is_active=True)
  639. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  640. racks = (
  641. Rack(site=site, name='Rack 1'),
  642. Rack(site=site, name='Rack 2'),
  643. )
  644. Rack.objects.bulk_create(racks)
  645. rack_reservations = (
  646. RackReservation(
  647. rack=racks[0],
  648. units=[1, 2, 3],
  649. user=user,
  650. description='Reservation #1',
  651. ),
  652. RackReservation(
  653. rack=racks[0],
  654. units=[4, 5, 6],
  655. user=user,
  656. description='Reservation #2'
  657. ),
  658. RackReservation(
  659. rack=racks[0],
  660. units=[7, 8, 9],
  661. user=user,
  662. description='Reservation #3',
  663. ),
  664. )
  665. RackReservation.objects.bulk_create(rack_reservations)
  666. cls.create_data = [
  667. {
  668. 'rack': racks[1].pk,
  669. 'units': [10, 11, 12],
  670. 'status': RackReservationStatusChoices.STATUS_ACTIVE,
  671. 'user': user.pk,
  672. 'description': 'Reservation #4',
  673. },
  674. {
  675. 'rack': racks[1].pk,
  676. 'units': [13, 14, 15],
  677. 'status': RackReservationStatusChoices.STATUS_PENDING,
  678. 'user': user.pk,
  679. 'description': 'Reservation #5',
  680. },
  681. {
  682. 'rack': racks[1].pk,
  683. 'units': [16, 17, 18],
  684. 'status': RackReservationStatusChoices.STATUS_STALE,
  685. 'user': user.pk,
  686. 'description': 'Reservation #6',
  687. },
  688. ]
  689. def test_unit_count(self):
  690. """unit_count should reflect the number of units in the reservation."""
  691. url = reverse('dcim-api:rackreservation-list')
  692. self.add_permissions('dcim.view_rackreservation')
  693. response = self.client.get(url, **self.header)
  694. self.assertHttpStatus(response, 200)
  695. for result in response.data['results']:
  696. self.assertEqual(result['unit_count'], len(result['units']))
  697. class ManufacturerTest(APIViewTestCases.APIViewTestCase):
  698. model = Manufacturer
  699. brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url']
  700. create_data = [
  701. {
  702. 'name': 'Manufacturer 4',
  703. 'slug': 'manufacturer-4',
  704. },
  705. {
  706. 'name': 'Manufacturer 5',
  707. 'slug': 'manufacturer-5',
  708. },
  709. {
  710. 'name': 'Manufacturer 6',
  711. 'slug': 'manufacturer-6',
  712. },
  713. ]
  714. bulk_update_data = {
  715. 'description': 'New description',
  716. }
  717. @classmethod
  718. def setUpTestData(cls):
  719. manufacturers = (
  720. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  721. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  722. Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
  723. )
  724. Manufacturer.objects.bulk_create(manufacturers)
  725. class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
  726. model = DeviceType
  727. brief_fields = ['description', 'device_count', 'display', 'id', 'manufacturer', 'model', 'slug', 'url']
  728. bulk_update_data = {
  729. 'part_number': 'ABC123',
  730. }
  731. user_permissions = ('dcim.view_manufacturer', )
  732. @classmethod
  733. def setUpTestData(cls):
  734. manufacturers = (
  735. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  736. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  737. )
  738. Manufacturer.objects.bulk_create(manufacturers)
  739. device_types = (
  740. DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
  741. DeviceType(manufacturer=manufacturers[0], model='Device Type 2', slug='device-type-2'),
  742. DeviceType(manufacturer=manufacturers[0], model='Device Type 3', slug='device-type-3'),
  743. )
  744. DeviceType.objects.bulk_create(device_types)
  745. cls.create_data = [
  746. {
  747. 'manufacturer': manufacturers[1].pk,
  748. 'model': 'Device Type 4',
  749. 'slug': 'device-type-4',
  750. 'u_height': 0,
  751. },
  752. {
  753. 'manufacturer': manufacturers[1].pk,
  754. 'model': 'Device Type 5',
  755. 'slug': 'device-type-5',
  756. 'u_height': 0.5,
  757. },
  758. {
  759. 'manufacturer': manufacturers[1].pk,
  760. 'model': 'Device Type 6',
  761. 'slug': 'device-type-6',
  762. 'u_height': 1,
  763. },
  764. ]
  765. class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
  766. model = ModuleType
  767. brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'module_count', 'profile', 'url']
  768. bulk_update_data = {
  769. 'part_number': 'ABC123',
  770. }
  771. user_permissions = ('dcim.view_manufacturer', )
  772. @classmethod
  773. def setUpTestData(cls):
  774. manufacturers = (
  775. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  776. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  777. )
  778. Manufacturer.objects.bulk_create(manufacturers)
  779. module_types = (
  780. ModuleType(manufacturer=manufacturers[0], model='Module Type 1'),
  781. ModuleType(manufacturer=manufacturers[0], model='Module Type 2'),
  782. ModuleType(manufacturer=manufacturers[0], model='Module Type 3'),
  783. )
  784. ModuleType.objects.bulk_create(module_types)
  785. cls.create_data = [
  786. {
  787. 'manufacturer': manufacturers[1].pk,
  788. 'model': 'Module Type 4',
  789. },
  790. {
  791. 'manufacturer': manufacturers[1].pk,
  792. 'model': 'Module Type 5',
  793. },
  794. {
  795. 'manufacturer': manufacturers[1].pk,
  796. 'model': 'Module Type 6',
  797. },
  798. ]
  799. class ModuleTypeProfileTest(APIViewTestCases.APIViewTestCase):
  800. model = ModuleTypeProfile
  801. brief_fields = ['description', 'display', 'id', 'name', 'url']
  802. SCHEMAS = [
  803. {
  804. "properties": {
  805. "foo": {
  806. "type": "string"
  807. }
  808. }
  809. },
  810. {
  811. "properties": {
  812. "foo": {
  813. "type": "integer"
  814. }
  815. }
  816. },
  817. {
  818. "properties": {
  819. "foo": {
  820. "type": "boolean"
  821. }
  822. }
  823. },
  824. ]
  825. create_data = [
  826. {
  827. 'name': 'Module Type Profile 4',
  828. 'schema': SCHEMAS[0],
  829. },
  830. {
  831. 'name': 'Module Type Profile 5',
  832. 'schema': SCHEMAS[1],
  833. },
  834. {
  835. 'name': 'Module Type Profile 6',
  836. 'schema': SCHEMAS[2],
  837. },
  838. ]
  839. bulk_update_data = {
  840. 'description': 'New description',
  841. 'comments': 'New comments',
  842. }
  843. @classmethod
  844. def setUpTestData(cls):
  845. module_type_profiles = (
  846. ModuleTypeProfile(
  847. name='Module Type Profile 1',
  848. schema=cls.SCHEMAS[0]
  849. ),
  850. ModuleTypeProfile(
  851. name='Module Type Profile 2',
  852. schema=cls.SCHEMAS[1]
  853. ),
  854. ModuleTypeProfile(
  855. name='Module Type Profile 3',
  856. schema=cls.SCHEMAS[2]
  857. ),
  858. )
  859. ModuleTypeProfile.objects.bulk_create(module_type_profiles)
  860. class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
  861. model = ConsolePortTemplate
  862. brief_fields = ['description', 'display', 'id', 'name', 'url']
  863. bulk_update_data = {
  864. 'description': 'New description',
  865. }
  866. @classmethod
  867. def setUpTestData(cls):
  868. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  869. devicetype = DeviceType.objects.create(
  870. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  871. )
  872. moduletype = ModuleType.objects.create(
  873. manufacturer=manufacturer, model='Module Type 1'
  874. )
  875. console_port_templates = (
  876. ConsolePortTemplate(device_type=devicetype, name='Console Port Template 1'),
  877. ConsolePortTemplate(device_type=devicetype, name='Console Port Template 2'),
  878. ConsolePortTemplate(device_type=devicetype, name='Console Port Template 3'),
  879. )
  880. ConsolePortTemplate.objects.bulk_create(console_port_templates)
  881. cls.create_data = [
  882. {
  883. 'device_type': devicetype.pk,
  884. 'name': 'Console Port Template 4',
  885. },
  886. {
  887. 'device_type': devicetype.pk,
  888. 'name': 'Console Port Template 5',
  889. },
  890. {
  891. 'module_type': moduletype.pk,
  892. 'name': 'Console Port Template 6',
  893. },
  894. {
  895. 'module_type': moduletype.pk,
  896. 'name': 'Console Port Template 7',
  897. },
  898. ]
  899. class ConsoleServerPortTemplateTest(APIViewTestCases.APIViewTestCase):
  900. model = ConsoleServerPortTemplate
  901. brief_fields = ['description', 'display', 'id', 'name', 'url']
  902. bulk_update_data = {
  903. 'description': 'New description',
  904. }
  905. @classmethod
  906. def setUpTestData(cls):
  907. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  908. devicetype = DeviceType.objects.create(
  909. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  910. )
  911. moduletype = ModuleType.objects.create(
  912. manufacturer=manufacturer, model='Module Type 1'
  913. )
  914. console_server_port_templates = (
  915. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 1'),
  916. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 2'),
  917. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 3'),
  918. )
  919. ConsoleServerPortTemplate.objects.bulk_create(console_server_port_templates)
  920. cls.create_data = [
  921. {
  922. 'device_type': devicetype.pk,
  923. 'name': 'Console Server Port Template 4',
  924. },
  925. {
  926. 'device_type': devicetype.pk,
  927. 'name': 'Console Server Port Template 5',
  928. },
  929. {
  930. 'module_type': moduletype.pk,
  931. 'name': 'Console Server Port Template 6',
  932. },
  933. {
  934. 'module_type': moduletype.pk,
  935. 'name': 'Console Server Port Template 7',
  936. },
  937. ]
  938. class PowerPortTemplateTest(APIViewTestCases.APIViewTestCase):
  939. model = PowerPortTemplate
  940. brief_fields = ['description', 'display', 'id', 'name', 'url']
  941. bulk_update_data = {
  942. 'description': 'New description',
  943. }
  944. @classmethod
  945. def setUpTestData(cls):
  946. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  947. devicetype = DeviceType.objects.create(
  948. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  949. )
  950. moduletype = ModuleType.objects.create(
  951. manufacturer=manufacturer, model='Module Type 1'
  952. )
  953. power_port_templates = (
  954. PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
  955. PowerPortTemplate(device_type=devicetype, name='Power Port Template 2'),
  956. PowerPortTemplate(device_type=devicetype, name='Power Port Template 3'),
  957. )
  958. PowerPortTemplate.objects.bulk_create(power_port_templates)
  959. cls.create_data = [
  960. {
  961. 'device_type': devicetype.pk,
  962. 'name': 'Power Port Template 4',
  963. },
  964. {
  965. 'device_type': devicetype.pk,
  966. 'name': 'Power Port Template 5',
  967. },
  968. {
  969. 'module_type': moduletype.pk,
  970. 'name': 'Power Port Template 6',
  971. },
  972. {
  973. 'module_type': moduletype.pk,
  974. 'name': 'Power Port Template 7',
  975. },
  976. ]
  977. class PowerOutletTemplateTest(APIViewTestCases.APIViewTestCase):
  978. model = PowerOutletTemplate
  979. brief_fields = ['description', 'display', 'id', 'name', 'url']
  980. bulk_update_data = {
  981. 'description': 'New description',
  982. }
  983. user_permissions = ('dcim.view_devicetype', )
  984. @classmethod
  985. def setUpTestData(cls):
  986. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  987. devicetype = DeviceType.objects.create(
  988. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  989. )
  990. moduletype = ModuleType.objects.create(
  991. manufacturer=manufacturer, model='Module Type 1'
  992. )
  993. power_port_templates = (
  994. PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
  995. PowerPortTemplate(device_type=devicetype, name='Power Port Template 2'),
  996. )
  997. PowerPortTemplate.objects.bulk_create(power_port_templates)
  998. power_outlet_templates = (
  999. PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 1'),
  1000. PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 2'),
  1001. PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 3'),
  1002. )
  1003. PowerOutletTemplate.objects.bulk_create(power_outlet_templates)
  1004. cls.create_data = [
  1005. {
  1006. 'device_type': devicetype.pk,
  1007. 'name': 'Power Outlet Template 4',
  1008. 'power_port': power_port_templates[0].pk,
  1009. },
  1010. {
  1011. 'device_type': devicetype.pk,
  1012. 'name': 'Power Outlet Template 5',
  1013. 'power_port': power_port_templates[1].pk,
  1014. },
  1015. {
  1016. 'device_type': devicetype.pk,
  1017. 'name': 'Power Outlet Template 6',
  1018. 'power_port': None,
  1019. },
  1020. {
  1021. 'module_type': moduletype.pk,
  1022. 'name': 'Power Outlet Template 7',
  1023. },
  1024. {
  1025. 'module_type': moduletype.pk,
  1026. 'name': 'Power Outlet Template 8',
  1027. },
  1028. ]
  1029. class InterfaceTemplateTest(APIViewTestCases.APIViewTestCase):
  1030. model = InterfaceTemplate
  1031. brief_fields = ['description', 'display', 'id', 'name', 'url']
  1032. bulk_update_data = {
  1033. 'description': 'New description',
  1034. }
  1035. @classmethod
  1036. def setUpTestData(cls):
  1037. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1038. devicetype = DeviceType.objects.create(
  1039. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  1040. )
  1041. moduletype = ModuleType.objects.create(
  1042. manufacturer=manufacturer, model='Module Type 1'
  1043. )
  1044. interface_templates = (
  1045. InterfaceTemplate(device_type=devicetype, name='Interface Template 1', type='1000base-t'),
  1046. InterfaceTemplate(device_type=devicetype, name='Interface Template 2', type='1000base-t'),
  1047. InterfaceTemplate(device_type=devicetype, name='Interface Template 3', type='1000base-t'),
  1048. )
  1049. InterfaceTemplate.objects.bulk_create(interface_templates)
  1050. cls.create_data = [
  1051. {
  1052. 'device_type': devicetype.pk,
  1053. 'name': 'Interface Template 4',
  1054. 'type': '1000base-t',
  1055. },
  1056. {
  1057. 'device_type': devicetype.pk,
  1058. 'name': 'Interface Template 5',
  1059. 'type': '1000base-t',
  1060. },
  1061. {
  1062. 'module_type': moduletype.pk,
  1063. 'name': 'Interface Template 6',
  1064. 'type': '1000base-t',
  1065. },
  1066. {
  1067. 'module_type': moduletype.pk,
  1068. 'name': 'Interface Template 7',
  1069. 'type': '1000base-t',
  1070. },
  1071. ]
  1072. class FrontPortTemplateTest(APIViewTestCases.APIViewTestCase):
  1073. model = FrontPortTemplate
  1074. brief_fields = ['description', 'display', 'id', 'name', 'url']
  1075. bulk_update_data = {
  1076. 'description': 'New description',
  1077. }
  1078. user_permissions = ('dcim.view_rearporttemplate', )
  1079. @classmethod
  1080. def setUpTestData(cls):
  1081. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1082. devicetype = DeviceType.objects.create(
  1083. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  1084. )
  1085. moduletype = ModuleType.objects.create(
  1086. manufacturer=manufacturer, model='Module Type 1'
  1087. )
  1088. rear_port_templates = (
  1089. RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C),
  1090. RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C),
  1091. RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C),
  1092. RearPortTemplate(device_type=devicetype, name='Rear Port Template 4', type=PortTypeChoices.TYPE_8P8C),
  1093. RearPortTemplate(device_type=devicetype, name='Rear Port Template 5', type=PortTypeChoices.TYPE_8P8C),
  1094. RearPortTemplate(device_type=devicetype, name='Rear Port Template 6', type=PortTypeChoices.TYPE_8P8C),
  1095. )
  1096. RearPortTemplate.objects.bulk_create(rear_port_templates)
  1097. front_port_templates = (
  1098. FrontPortTemplate(device_type=devicetype, name='Front Port Template 1', type=PortTypeChoices.TYPE_8P8C),
  1099. FrontPortTemplate(device_type=devicetype, name='Front Port Template 2', type=PortTypeChoices.TYPE_8P8C),
  1100. FrontPortTemplate(module_type=moduletype, name='Front Port Template 3', type=PortTypeChoices.TYPE_8P8C),
  1101. )
  1102. FrontPortTemplate.objects.bulk_create(front_port_templates)
  1103. PortTemplateMapping.objects.bulk_create([
  1104. PortTemplateMapping(
  1105. device_type=devicetype,
  1106. front_port=front_port_templates[0],
  1107. rear_port=rear_port_templates[0],
  1108. ),
  1109. PortTemplateMapping(
  1110. device_type=devicetype,
  1111. front_port=front_port_templates[1],
  1112. rear_port=rear_port_templates[1],
  1113. ),
  1114. PortTemplateMapping(
  1115. module_type=moduletype,
  1116. front_port=front_port_templates[2],
  1117. rear_port=rear_port_templates[2],
  1118. ),
  1119. ])
  1120. cls.create_data = [
  1121. {
  1122. 'device_type': devicetype.pk,
  1123. 'name': 'Front Port Template 3',
  1124. 'type': PortTypeChoices.TYPE_8P8C,
  1125. 'rear_ports': [
  1126. {
  1127. 'position': 1,
  1128. 'rear_port': rear_port_templates[3].pk,
  1129. 'rear_port_position': 1,
  1130. },
  1131. ],
  1132. },
  1133. {
  1134. 'device_type': devicetype.pk,
  1135. 'name': 'Front Port Template 4',
  1136. 'type': PortTypeChoices.TYPE_8P8C,
  1137. 'rear_ports': [
  1138. {
  1139. 'position': 1,
  1140. 'rear_port': rear_port_templates[4].pk,
  1141. 'rear_port_position': 1,
  1142. },
  1143. ],
  1144. },
  1145. {
  1146. 'module_type': moduletype.pk,
  1147. 'name': 'Front Port Template 7',
  1148. 'type': PortTypeChoices.TYPE_8P8C,
  1149. 'rear_ports': [
  1150. {
  1151. 'position': 1,
  1152. 'rear_port': rear_port_templates[5].pk,
  1153. 'rear_port_position': 1,
  1154. },
  1155. ],
  1156. },
  1157. ]
  1158. cls.update_data = {
  1159. 'type': PortTypeChoices.TYPE_LC,
  1160. 'rear_ports': [
  1161. {
  1162. 'position': 1,
  1163. 'rear_port': rear_port_templates[3].pk,
  1164. 'rear_port_position': 1,
  1165. },
  1166. ],
  1167. }
  1168. def test_update_object(self):
  1169. super().test_update_object()
  1170. # Check that the port mapping was updated after modifying the front port template
  1171. front_port_template = FrontPortTemplate.objects.get(name='Front Port Template 1')
  1172. rear_port_template = RearPortTemplate.objects.get(name='Rear Port Template 4')
  1173. self.assertTrue(
  1174. PortTemplateMapping.objects.filter(
  1175. front_port=front_port_template,
  1176. front_port_position=1,
  1177. rear_port=rear_port_template,
  1178. rear_port_position=1,
  1179. ).exists()
  1180. )
  1181. class RearPortTemplateTest(APIViewTestCases.APIViewTestCase):
  1182. model = RearPortTemplate
  1183. brief_fields = ['description', 'display', 'id', 'name', 'url']
  1184. bulk_update_data = {
  1185. 'description': 'New description',
  1186. }
  1187. @classmethod
  1188. def setUpTestData(cls):
  1189. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1190. devicetype = DeviceType.objects.create(
  1191. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  1192. )
  1193. moduletype = ModuleType.objects.create(
  1194. manufacturer=manufacturer, model='Module Type 1'
  1195. )
  1196. front_port_templates = (
  1197. FrontPortTemplate(device_type=devicetype, name='Front Port Template 1', type=PortTypeChoices.TYPE_8P8C),
  1198. FrontPortTemplate(device_type=devicetype, name='Front Port Template 2', type=PortTypeChoices.TYPE_8P8C),
  1199. FrontPortTemplate(module_type=moduletype, name='Front Port Template 3', type=PortTypeChoices.TYPE_8P8C),
  1200. FrontPortTemplate(module_type=moduletype, name='Front Port Template 4', type=PortTypeChoices.TYPE_8P8C),
  1201. FrontPortTemplate(module_type=moduletype, name='Front Port Template 5', type=PortTypeChoices.TYPE_8P8C),
  1202. FrontPortTemplate(module_type=moduletype, name='Front Port Template 6', type=PortTypeChoices.TYPE_8P8C),
  1203. )
  1204. FrontPortTemplate.objects.bulk_create(front_port_templates)
  1205. rear_port_templates = (
  1206. RearPortTemplate(device_type=devicetype, name='Rear Port Template 1', type=PortTypeChoices.TYPE_8P8C),
  1207. RearPortTemplate(device_type=devicetype, name='Rear Port Template 2', type=PortTypeChoices.TYPE_8P8C),
  1208. RearPortTemplate(device_type=devicetype, name='Rear Port Template 3', type=PortTypeChoices.TYPE_8P8C),
  1209. )
  1210. RearPortTemplate.objects.bulk_create(rear_port_templates)
  1211. PortTemplateMapping.objects.bulk_create([
  1212. PortTemplateMapping(
  1213. device_type=devicetype,
  1214. front_port=front_port_templates[0],
  1215. rear_port=rear_port_templates[0],
  1216. ),
  1217. PortTemplateMapping(
  1218. device_type=devicetype,
  1219. front_port=front_port_templates[1],
  1220. rear_port=rear_port_templates[1],
  1221. ),
  1222. PortTemplateMapping(
  1223. module_type=moduletype,
  1224. front_port=front_port_templates[2],
  1225. rear_port=rear_port_templates[2],
  1226. ),
  1227. ])
  1228. cls.create_data = [
  1229. {
  1230. 'device_type': devicetype.pk,
  1231. 'name': 'Rear Port Template 4',
  1232. 'type': PortTypeChoices.TYPE_8P8C,
  1233. 'front_ports': [
  1234. {
  1235. 'position': 1,
  1236. 'front_port': front_port_templates[3].pk,
  1237. 'front_port_position': 1,
  1238. },
  1239. ],
  1240. },
  1241. {
  1242. 'device_type': devicetype.pk,
  1243. 'name': 'Rear Port Template 5',
  1244. 'type': PortTypeChoices.TYPE_8P8C,
  1245. 'front_ports': [
  1246. {
  1247. 'position': 1,
  1248. 'front_port': front_port_templates[4].pk,
  1249. 'front_port_position': 1,
  1250. },
  1251. ],
  1252. },
  1253. {
  1254. 'module_type': moduletype.pk,
  1255. 'name': 'Rear Port Template 6',
  1256. 'type': PortTypeChoices.TYPE_8P8C,
  1257. 'front_ports': [
  1258. {
  1259. 'position': 1,
  1260. 'front_port': front_port_templates[5].pk,
  1261. 'front_port_position': 1,
  1262. },
  1263. ],
  1264. },
  1265. ]
  1266. cls.update_data = {
  1267. 'type': PortTypeChoices.TYPE_LC,
  1268. 'front_ports': [
  1269. {
  1270. 'position': 1,
  1271. 'front_port': front_port_templates[3].pk,
  1272. 'front_port_position': 1,
  1273. },
  1274. ],
  1275. }
  1276. def test_update_object(self):
  1277. super().test_update_object()
  1278. # Check that the port mapping was updated after modifying the rear port template
  1279. front_port_template = FrontPortTemplate.objects.get(name='Front Port Template 4')
  1280. rear_port_template = RearPortTemplate.objects.get(name='Rear Port Template 1')
  1281. self.assertTrue(
  1282. PortTemplateMapping.objects.filter(
  1283. front_port=front_port_template,
  1284. front_port_position=1,
  1285. rear_port=rear_port_template,
  1286. rear_port_position=1,
  1287. ).exists()
  1288. )
  1289. class ModuleBayTemplateTest(APIViewTestCases.APIViewTestCase):
  1290. model = ModuleBayTemplate
  1291. brief_fields = ['description', 'display', 'enabled', 'id', 'name', 'url']
  1292. bulk_update_data = {
  1293. 'description': 'New description',
  1294. }
  1295. user_permissions = ('dcim.view_devicetype', )
  1296. @classmethod
  1297. def setUpTestData(cls):
  1298. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1299. devicetype = DeviceType.objects.create(
  1300. manufacturer=manufacturer,
  1301. model='Device Type 1',
  1302. slug='device-type-1',
  1303. subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
  1304. )
  1305. module_bay_templates = (
  1306. ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 1', enabled=True),
  1307. ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 2', enabled=False),
  1308. ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 3', enabled=True),
  1309. )
  1310. ModuleBayTemplate.objects.bulk_create(module_bay_templates)
  1311. cls.create_data = [
  1312. {
  1313. 'device_type': devicetype.pk,
  1314. 'name': 'Module Bay Template 4',
  1315. 'enabled': False,
  1316. },
  1317. {
  1318. 'device_type': devicetype.pk,
  1319. 'name': 'Module Bay Template 5',
  1320. },
  1321. {
  1322. 'device_type': devicetype.pk,
  1323. 'name': 'Module Bay Template 6',
  1324. },
  1325. ]
  1326. class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
  1327. model = DeviceBayTemplate
  1328. brief_fields = ['description', 'display', 'enabled', 'id', 'name', 'url']
  1329. bulk_update_data = {
  1330. 'description': 'New description',
  1331. }
  1332. user_permissions = ('dcim.view_devicetype', )
  1333. @classmethod
  1334. def setUpTestData(cls):
  1335. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1336. devicetype = DeviceType.objects.create(
  1337. manufacturer=manufacturer,
  1338. model='Device Type 1',
  1339. slug='device-type-1',
  1340. subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
  1341. )
  1342. device_bay_templates = (
  1343. DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1', enabled=True),
  1344. DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2', enabled=False),
  1345. DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3', enabled=True),
  1346. )
  1347. DeviceBayTemplate.objects.bulk_create(device_bay_templates)
  1348. cls.create_data = [
  1349. {
  1350. 'device_type': devicetype.pk,
  1351. 'name': 'Device Bay Template 4',
  1352. 'enabled': False,
  1353. },
  1354. {
  1355. 'device_type': devicetype.pk,
  1356. 'name': 'Device Bay Template 5',
  1357. },
  1358. {
  1359. 'device_type': devicetype.pk,
  1360. 'name': 'Device Bay Template 6',
  1361. },
  1362. ]
  1363. class InventoryItemTemplateTest(APIViewTestCases.APIViewTestCase):
  1364. model = InventoryItemTemplate
  1365. brief_fields = ['_depth', 'description', 'display', 'id', 'name', 'url']
  1366. bulk_update_data = {
  1367. 'description': 'New description',
  1368. }
  1369. user_permissions = ('dcim.view_devicetype', 'dcim.view_manufacturer',)
  1370. @classmethod
  1371. def setUpTestData(cls):
  1372. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1373. devicetype = DeviceType.objects.create(
  1374. manufacturer=manufacturer,
  1375. model='Device Type 1',
  1376. slug='device-type-1'
  1377. )
  1378. role = InventoryItemRole.objects.create(name='Inventory Item Role 1', slug='inventory-item-role-1')
  1379. inventory_item_templates = (
  1380. InventoryItemTemplate(
  1381. device_type=devicetype, name='Inventory Item Template 1', manufacturer=manufacturer, role=role
  1382. ),
  1383. InventoryItemTemplate(
  1384. device_type=devicetype, name='Inventory Item Template 2', manufacturer=manufacturer, role=role
  1385. ),
  1386. InventoryItemTemplate(
  1387. device_type=devicetype, name='Inventory Item Template 3', manufacturer=manufacturer, role=role
  1388. ),
  1389. InventoryItemTemplate(
  1390. device_type=devicetype, name='Inventory Item Template 4', manufacturer=manufacturer, role=role
  1391. ),
  1392. )
  1393. for item in inventory_item_templates:
  1394. item.save()
  1395. cls.create_data = [
  1396. {
  1397. 'device_type': devicetype.pk,
  1398. 'name': 'Inventory Item Template 5',
  1399. 'manufacturer': manufacturer.pk,
  1400. 'role': role.pk,
  1401. 'parent': inventory_item_templates[3].pk,
  1402. },
  1403. {
  1404. 'device_type': devicetype.pk,
  1405. 'name': 'Inventory Item Template 6',
  1406. 'manufacturer': manufacturer.pk,
  1407. 'role': role.pk,
  1408. 'parent': inventory_item_templates[3].pk,
  1409. },
  1410. {
  1411. 'device_type': devicetype.pk,
  1412. 'name': 'Inventory Item Template 7',
  1413. 'manufacturer': manufacturer.pk,
  1414. 'role': role.pk,
  1415. 'parent': inventory_item_templates[3].pk,
  1416. },
  1417. ]
  1418. class DeviceRoleTest(APIViewTestCases.APIViewTestCase):
  1419. model = DeviceRole
  1420. brief_fields = [
  1421. '_depth', 'description', 'device_count', 'display', 'id', 'name', 'slug', 'url', 'virtualmachine_count'
  1422. ]
  1423. create_data = [
  1424. {
  1425. 'name': 'Device Role 4',
  1426. 'slug': 'device-role-4',
  1427. 'color': 'ffff00',
  1428. },
  1429. {
  1430. 'name': 'Device Role 5',
  1431. 'slug': 'device-role-5',
  1432. 'color': 'ffff00',
  1433. },
  1434. {
  1435. 'name': 'Device Role 6',
  1436. 'slug': 'device-role-6',
  1437. 'color': 'ffff00',
  1438. },
  1439. ]
  1440. bulk_update_data = {
  1441. 'description': 'New description',
  1442. }
  1443. @classmethod
  1444. def setUpTestData(cls):
  1445. DeviceRole.objects.create(name='Device Role 1', slug='device-role-1', color='ff0000')
  1446. DeviceRole.objects.create(name='Device Role 2', slug='device-role-2', color='00ff00')
  1447. DeviceRole.objects.create(name='Device Role 3', slug='device-role-3', color='0000ff')
  1448. class PlatformTest(APIViewTestCases.APIViewTestCase):
  1449. model = Platform
  1450. brief_fields = [
  1451. '_depth', 'description', 'device_count', 'display', 'id', 'name', 'slug', 'url', 'virtualmachine_count',
  1452. ]
  1453. create_data = [
  1454. {
  1455. 'name': 'Platform 4',
  1456. 'slug': 'platform-4',
  1457. },
  1458. {
  1459. 'name': 'Platform 5',
  1460. 'slug': 'platform-5',
  1461. },
  1462. {
  1463. 'name': 'Platform 6',
  1464. 'slug': 'platform-6',
  1465. },
  1466. ]
  1467. bulk_update_data = {
  1468. 'description': 'New description',
  1469. }
  1470. @classmethod
  1471. def setUpTestData(cls):
  1472. platforms = (
  1473. Platform(name='Platform 1', slug='platform-1'),
  1474. Platform(name='Platform 2', slug='platform-2'),
  1475. Platform(name='Platform 3', slug='platform-3'),
  1476. )
  1477. for platform in platforms:
  1478. platform.save()
  1479. class DeviceTest(APIViewTestCases.APIViewTestCase):
  1480. model = Device
  1481. brief_fields = ['description', 'display', 'id', 'name', 'url']
  1482. bulk_update_data = {
  1483. 'status': 'failed',
  1484. }
  1485. user_permissions = (
  1486. 'dcim.view_site', 'dcim.view_rack', 'dcim.view_location', 'dcim.view_devicerole', 'dcim.view_devicetype',
  1487. )
  1488. @classmethod
  1489. def setUpTestData(cls):
  1490. sites = (
  1491. Site(name='Site 1', slug='site-1'),
  1492. Site(name='Site 2', slug='site-2'),
  1493. )
  1494. Site.objects.bulk_create(sites)
  1495. racks = (
  1496. Rack(name='Rack 1', site=sites[0]),
  1497. Rack(name='Rack 2', site=sites[1]),
  1498. )
  1499. Rack.objects.bulk_create(racks)
  1500. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1501. device_types = (
  1502. DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
  1503. DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=2),
  1504. )
  1505. DeviceType.objects.bulk_create(device_types)
  1506. roles = (
  1507. DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000'),
  1508. DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00'),
  1509. )
  1510. for role in roles:
  1511. role.save()
  1512. cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
  1513. clusters = (
  1514. Cluster(name='Cluster 1', type=cluster_type),
  1515. Cluster(name='Cluster 2', type=cluster_type),
  1516. )
  1517. Cluster.objects.bulk_create(clusters)
  1518. devices = (
  1519. Device(
  1520. device_type=device_types[0],
  1521. role=roles[0],
  1522. name='Device 1',
  1523. site=sites[0],
  1524. rack=racks[0],
  1525. cluster=clusters[0],
  1526. local_context_data={'A': 1}
  1527. ),
  1528. Device(
  1529. device_type=device_types[0],
  1530. role=roles[0],
  1531. name='Device 2',
  1532. site=sites[0],
  1533. rack=racks[0],
  1534. cluster=clusters[0],
  1535. local_context_data={'B': 2}
  1536. ),
  1537. Device(
  1538. device_type=device_types[0],
  1539. role=roles[0],
  1540. name='Device 3',
  1541. site=sites[0],
  1542. rack=racks[0],
  1543. cluster=clusters[0],
  1544. local_context_data={'C': 3}
  1545. ),
  1546. )
  1547. Device.objects.bulk_create(devices)
  1548. cls.create_data = [
  1549. {
  1550. 'device_type': device_types[1].pk,
  1551. 'role': roles[1].pk,
  1552. 'name': 'Test Device 4',
  1553. 'site': sites[1].pk,
  1554. 'rack': racks[1].pk,
  1555. 'cluster': clusters[1].pk,
  1556. },
  1557. {
  1558. 'device_type': device_types[1].pk,
  1559. 'role': roles[1].pk,
  1560. 'name': 'Test Device 5',
  1561. 'site': sites[1].pk,
  1562. 'rack': racks[1].pk,
  1563. 'cluster': clusters[1].pk,
  1564. },
  1565. {
  1566. 'device_type': device_types[1].pk,
  1567. 'role': roles[1].pk,
  1568. 'name': 'Test Device 6',
  1569. 'site': sites[1].pk,
  1570. 'rack': racks[1].pk,
  1571. 'cluster': clusters[1].pk,
  1572. },
  1573. ]
  1574. def test_config_context_included_by_default_in_list_view(self):
  1575. """
  1576. Check that config context data is included by default in the devices list.
  1577. """
  1578. self.add_permissions('dcim.view_device')
  1579. url = reverse('dcim-api:device-list') + '?slug=device-with-context-data'
  1580. response = self.client.get(url, **self.header)
  1581. self.assertEqual(response.data['results'][0].get('config_context', {}).get('A'), 1)
  1582. def test_config_context_excluded(self):
  1583. """
  1584. Check that config context data can be excluded by passing ?exclude=config_context.
  1585. """
  1586. self.add_permissions('dcim.view_device')
  1587. url = reverse('dcim-api:device-list') + '?exclude=config_context'
  1588. response = self.client.get(url, **self.header)
  1589. self.assertFalse('config_context' in response.data['results'][0])
  1590. def test_unique_name_per_site_constraint(self):
  1591. """
  1592. Check that creating a device with a duplicate name within a site fails.
  1593. """
  1594. device = Device.objects.first()
  1595. data = {
  1596. 'device_type': device.device_type.pk,
  1597. 'role': device.role.pk,
  1598. 'site': device.site.pk,
  1599. 'name': device.name,
  1600. }
  1601. self.add_permissions('dcim.add_device')
  1602. url = reverse('dcim-api:device-list')
  1603. response = self.client.post(url, data, format='json', **self.header)
  1604. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  1605. def test_rack_fit(self):
  1606. """
  1607. Check that creating multiple devices with overlapping position fails.
  1608. """
  1609. device = Device.objects.first()
  1610. device_type = DeviceType.objects.all()[1]
  1611. data = [
  1612. {
  1613. 'device_type': device_type.pk,
  1614. 'role': device.role.pk,
  1615. 'site': device.site.pk,
  1616. 'name': 'Test Device 7',
  1617. 'rack': device.rack.pk,
  1618. 'face': 'front',
  1619. 'position': 1
  1620. },
  1621. {
  1622. 'device_type': device_type.pk,
  1623. 'role': device.role.pk,
  1624. 'site': device.site.pk,
  1625. 'name': 'Test Device 8',
  1626. 'rack': device.rack.pk,
  1627. 'face': 'front',
  1628. 'position': 2
  1629. }
  1630. ]
  1631. self.add_permissions('dcim.add_device')
  1632. url = reverse('dcim-api:device-list')
  1633. response = self.client.post(url, data, format='json', **self.header)
  1634. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  1635. def test_render_config(self):
  1636. configtemplate = ConfigTemplate.objects.create(
  1637. name='Config Template 1',
  1638. template_code='Config for device {{ device.name }}'
  1639. )
  1640. device = Device.objects.first()
  1641. device.config_template = configtemplate
  1642. device.save()
  1643. self.add_permissions('dcim.render_config_device', 'dcim.view_device')
  1644. url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
  1645. response = self.client.post(url, {}, format='json', **self.header)
  1646. self.assertHttpStatus(response, status.HTTP_200_OK)
  1647. self.assertEqual(response.data['content'], f'Config for device {device.name}')
  1648. def test_render_config_without_permission(self):
  1649. configtemplate = ConfigTemplate.objects.create(
  1650. name='Config Template 1',
  1651. template_code='Config for device {{ device.name }}'
  1652. )
  1653. device = Device.objects.first()
  1654. device.config_template = configtemplate
  1655. device.save()
  1656. # No permissions added - user has no render_config permission
  1657. url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
  1658. response = self.client.post(url, {}, format='json', **self.header)
  1659. self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
  1660. def test_render_config_token_write_enabled(self):
  1661. configtemplate = ConfigTemplate.objects.create(
  1662. name='Config Template 1',
  1663. template_code='Config for device {{ device.name }}'
  1664. )
  1665. device = Device.objects.first()
  1666. device.config_template = configtemplate
  1667. device.save()
  1668. self.add_permissions('dcim.render_config_device', 'dcim.view_device')
  1669. url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
  1670. # Request without token auth should fail with PermissionDenied
  1671. response = self.client.post(url, {}, format='json')
  1672. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  1673. # Create token with write_enabled=False
  1674. token = Token.objects.create(version=2, user=self.user, write_enabled=False)
  1675. token_header = f'Bearer {TOKEN_PREFIX}{token.key}.{token.token}'
  1676. # Request with write-disabled token should fail
  1677. response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
  1678. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  1679. # Enable write and retry
  1680. token.write_enabled = True
  1681. token.save()
  1682. response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
  1683. self.assertHttpStatus(response, status.HTTP_200_OK)
  1684. class ModuleTest(APIViewTestCases.APIViewTestCase):
  1685. model = Module
  1686. brief_fields = ['description', 'device', 'display', 'id', 'module_bay', 'module_type', 'url']
  1687. bulk_update_data = {
  1688. 'serial': '1234ABCD',
  1689. }
  1690. user_permissions = ('dcim.view_modulebay', 'dcim.view_moduletype', 'dcim.view_device')
  1691. @classmethod
  1692. def setUpTestData(cls):
  1693. manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
  1694. device = create_test_device('Test Device 1')
  1695. module_types = (
  1696. ModuleType(manufacturer=manufacturer, model='Module Type 1'),
  1697. ModuleType(manufacturer=manufacturer, model='Module Type 2'),
  1698. ModuleType(manufacturer=manufacturer, model='Module Type 3'),
  1699. )
  1700. ModuleType.objects.bulk_create(module_types)
  1701. module_bays = (
  1702. ModuleBay(device=device, name='Module Bay 1'),
  1703. ModuleBay(device=device, name='Module Bay 2'),
  1704. ModuleBay(device=device, name='Module Bay 3'),
  1705. ModuleBay(device=device, name='Module Bay 4'),
  1706. ModuleBay(device=device, name='Module Bay 5'),
  1707. ModuleBay(device=device, name='Module Bay 6'),
  1708. )
  1709. for module_bay in module_bays:
  1710. module_bay.save()
  1711. modules = (
  1712. Module(device=device, module_bay=module_bays[0], module_type=module_types[0]),
  1713. Module(device=device, module_bay=module_bays[1], module_type=module_types[1]),
  1714. Module(device=device, module_bay=module_bays[2], module_type=module_types[2]),
  1715. )
  1716. Module.objects.bulk_create(modules)
  1717. cls.create_data = [
  1718. {
  1719. 'device': device.pk,
  1720. 'module_bay': module_bays[3].pk,
  1721. 'module_type': module_types[0].pk,
  1722. 'status': ModuleStatusChoices.STATUS_ACTIVE,
  1723. 'serial': 'ABC123',
  1724. 'asset_tag': 'Foo1',
  1725. },
  1726. {
  1727. 'device': device.pk,
  1728. 'module_bay': module_bays[4].pk,
  1729. 'module_type': module_types[1].pk,
  1730. 'status': ModuleStatusChoices.STATUS_ACTIVE,
  1731. 'serial': 'DEF456',
  1732. 'asset_tag': 'Foo2',
  1733. },
  1734. {
  1735. 'device': device.pk,
  1736. 'module_bay': module_bays[5].pk,
  1737. 'module_type': module_types[2].pk,
  1738. 'status': ModuleStatusChoices.STATUS_ACTIVE,
  1739. 'serial': 'GHI789',
  1740. 'asset_tag': 'Foo3',
  1741. },
  1742. ]
  1743. def test_replicate_components(self):
  1744. """
  1745. Installing a module with replicate_components=True (the default) should create
  1746. components from the module type's templates on the parent device.
  1747. """
  1748. self.add_permissions('dcim.add_module')
  1749. manufacturer = Manufacturer.objects.get(name='Generic')
  1750. device = create_test_device('Device for Replication Test')
  1751. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Replication Test Module Type')
  1752. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1753. module_bay = ModuleBay.objects.create(device=device, name='Replication Bay')
  1754. url = reverse('dcim-api:module-list')
  1755. data = {
  1756. 'device': device.pk,
  1757. 'module_bay': module_bay.pk,
  1758. 'module_type': module_type.pk,
  1759. 'replicate_components': True,
  1760. }
  1761. response = self.client.post(url, data, format='json', **self.header)
  1762. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  1763. self.assertTrue(device.interfaces.filter(name='eth0').exists())
  1764. def test_no_replicate_components(self):
  1765. """
  1766. Installing a module with replicate_components=False should NOT create components
  1767. from the module type's templates.
  1768. """
  1769. self.add_permissions('dcim.add_module')
  1770. manufacturer = Manufacturer.objects.get(name='Generic')
  1771. device = create_test_device('Device for No Replication Test')
  1772. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='No Replication Test Module Type')
  1773. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1774. module_bay = ModuleBay.objects.create(device=device, name='No Replication Bay')
  1775. url = reverse('dcim-api:module-list')
  1776. data = {
  1777. 'device': device.pk,
  1778. 'module_bay': module_bay.pk,
  1779. 'module_type': module_type.pk,
  1780. 'replicate_components': False,
  1781. }
  1782. response = self.client.post(url, data, format='json', **self.header)
  1783. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  1784. self.assertFalse(device.interfaces.filter(name='eth0').exists())
  1785. def test_adopt_components(self):
  1786. """
  1787. Installing a module with adopt_components=True should assign existing unattached
  1788. device components to the new module.
  1789. """
  1790. self.add_permissions('dcim.add_module')
  1791. manufacturer = Manufacturer.objects.get(name='Generic')
  1792. device = create_test_device('Device for Adopt Test')
  1793. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Adopt Test Module Type')
  1794. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1795. module_bay = ModuleBay.objects.create(device=device, name='Adopt Bay')
  1796. existing_iface = Interface.objects.create(device=device, name='eth0', type='1000base-t')
  1797. url = reverse('dcim-api:module-list')
  1798. data = {
  1799. 'device': device.pk,
  1800. 'module_bay': module_bay.pk,
  1801. 'module_type': module_type.pk,
  1802. 'adopt_components': True,
  1803. 'replicate_components': False,
  1804. }
  1805. response = self.client.post(url, data, format='json', **self.header)
  1806. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  1807. existing_iface.refresh_from_db()
  1808. self.assertIsNotNone(existing_iface.module)
  1809. def test_replicate_components_conflict(self):
  1810. """
  1811. Installing a module with replicate_components=True when a component with the same name
  1812. already exists should return a validation error.
  1813. """
  1814. self.add_permissions('dcim.add_module')
  1815. manufacturer = Manufacturer.objects.get(name='Generic')
  1816. device = create_test_device('Device for Conflict Test')
  1817. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Conflict Test Module Type')
  1818. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1819. module_bay = ModuleBay.objects.create(device=device, name='Conflict Bay')
  1820. Interface.objects.create(device=device, name='eth0', type='1000base-t')
  1821. url = reverse('dcim-api:module-list')
  1822. data = {
  1823. 'device': device.pk,
  1824. 'module_bay': module_bay.pk,
  1825. 'module_type': module_type.pk,
  1826. 'replicate_components': True,
  1827. }
  1828. response = self.client.post(url, data, format='json', **self.header)
  1829. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  1830. def test_adopt_components_already_owned(self):
  1831. """
  1832. Installing a module with adopt_components=True when an existing component already
  1833. belongs to another module should return a validation error.
  1834. """
  1835. self.add_permissions('dcim.add_module')
  1836. manufacturer = Manufacturer.objects.get(name='Generic')
  1837. device = create_test_device('Device for Adopt Owned Test')
  1838. owner_module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Owner Module Type')
  1839. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Adopt Owned Test Module Type')
  1840. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1841. owner_bay = ModuleBay.objects.create(device=device, name='Owner Bay')
  1842. target_bay = ModuleBay.objects.create(device=device, name='Adopt Owned Bay')
  1843. # Install a module that owns the interface
  1844. owner_module = Module.objects.create(device=device, module_bay=owner_bay, module_type=owner_module_type)
  1845. Interface.objects.create(device=device, name='eth0', type='1000base-t', module=owner_module)
  1846. url = reverse('dcim-api:module-list')
  1847. data = {
  1848. 'device': device.pk,
  1849. 'module_bay': target_bay.pk,
  1850. 'module_type': module_type.pk,
  1851. 'adopt_components': True,
  1852. }
  1853. response = self.client.post(url, data, format='json', **self.header)
  1854. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  1855. def test_patch_ignores_replicate_and_adopt(self):
  1856. """
  1857. PATCH requests that include replicate_components or adopt_components should not
  1858. trigger component replication or adoption (these fields are create-only).
  1859. """
  1860. self.add_permissions('dcim.change_module')
  1861. manufacturer = Manufacturer.objects.get(name='Generic')
  1862. device = create_test_device('Device for PATCH Test')
  1863. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='PATCH Test Module Type')
  1864. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1865. module_bay = ModuleBay.objects.create(device=device, name='PATCH Bay')
  1866. # Create the module without replication so we can verify PATCH doesn't trigger it
  1867. module = Module(device=device, module_bay=module_bay, module_type=module_type)
  1868. module._disable_replication = True
  1869. module.save()
  1870. url = reverse('dcim-api:module-detail', kwargs={'pk': module.pk})
  1871. data = {
  1872. 'replicate_components': True,
  1873. 'adopt_components': True,
  1874. 'serial': 'PATCHED',
  1875. }
  1876. response = self.client.patch(url, data, format='json', **self.header)
  1877. self.assertHttpStatus(response, status.HTTP_200_OK)
  1878. self.assertEqual(response.data['serial'], 'PATCHED')
  1879. # No interfaces should have been created by the PATCH
  1880. self.assertFalse(device.interfaces.exists())
  1881. def test_adopt_and_replicate_components(self):
  1882. """
  1883. Installing a module with both adopt_components=True and replicate_components=True
  1884. should adopt existing unowned components and create new components for templates
  1885. that have no matching existing component.
  1886. """
  1887. self.add_permissions('dcim.add_module')
  1888. manufacturer = Manufacturer.objects.get(name='Generic')
  1889. device = create_test_device('Device for Adopt+Replicate Test')
  1890. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Adopt+Replicate Test Module Type')
  1891. InterfaceTemplate.objects.create(module_type=module_type, name='eth0', type='1000base-t')
  1892. InterfaceTemplate.objects.create(module_type=module_type, name='eth1', type='1000base-t')
  1893. module_bay = ModuleBay.objects.create(device=device, name='Adopt+Replicate Bay')
  1894. # eth0 already exists (unowned); eth1 does not
  1895. existing_iface = Interface.objects.create(device=device, name='eth0', type='1000base-t')
  1896. url = reverse('dcim-api:module-list')
  1897. data = {
  1898. 'device': device.pk,
  1899. 'module_bay': module_bay.pk,
  1900. 'module_type': module_type.pk,
  1901. 'adopt_components': True,
  1902. 'replicate_components': True,
  1903. }
  1904. response = self.client.post(url, data, format='json', **self.header)
  1905. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  1906. # eth0 should have been adopted (now owned by the new module)
  1907. existing_iface.refresh_from_db()
  1908. self.assertIsNotNone(existing_iface.module)
  1909. # eth1 should have been created
  1910. self.assertTrue(device.interfaces.filter(name='eth1').exists())
  1911. def test_module_token_no_position(self):
  1912. """
  1913. Installing a module whose type has a template with a MODULE_TOKEN placeholder into a
  1914. module bay with no position defined should return a validation error.
  1915. """
  1916. self.add_permissions('dcim.add_module')
  1917. manufacturer = Manufacturer.objects.get(name='Generic')
  1918. device = create_test_device('Device for Token No-Position Test')
  1919. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Token No-Position Module Type')
  1920. # Template name contains the MODULE_TOKEN placeholder
  1921. InterfaceTemplate.objects.create(
  1922. module_type=module_type, name=f'{MODULE_TOKEN}-eth0', type='1000base-t'
  1923. )
  1924. # Module bay has no position
  1925. module_bay = ModuleBay.objects.create(device=device, name='No-Position Bay')
  1926. url = reverse('dcim-api:module-list')
  1927. data = {
  1928. 'device': device.pk,
  1929. 'module_bay': module_bay.pk,
  1930. 'module_type': module_type.pk,
  1931. }
  1932. response = self.client.post(url, data, format='json', **self.header)
  1933. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  1934. def test_module_token_depth_mismatch(self):
  1935. """
  1936. Installing a module whose template name has more MODULE_TOKEN placeholders than the
  1937. depth of the module bay tree should return a validation error.
  1938. """
  1939. self.add_permissions('dcim.add_module')
  1940. manufacturer = Manufacturer.objects.get(name='Generic')
  1941. device = create_test_device('Device for Token Depth Mismatch Test')
  1942. module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Token Depth Mismatch Module Type')
  1943. # Template name has two placeholders but the bay is at depth 1
  1944. InterfaceTemplate.objects.create(
  1945. module_type=module_type, name=f'{MODULE_TOKEN}-{MODULE_TOKEN}-eth0', type='1000base-t'
  1946. )
  1947. module_bay = ModuleBay.objects.create(device=device, name='Depth 1 Bay', position='1')
  1948. url = reverse('dcim-api:module-list')
  1949. data = {
  1950. 'device': device.pk,
  1951. 'module_bay': module_bay.pk,
  1952. 'module_type': module_type.pk,
  1953. }
  1954. response = self.client.post(url, data, format='json', **self.header)
  1955. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  1956. class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
  1957. model = ConsolePort
  1958. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  1959. bulk_update_data = {
  1960. 'description': 'New description',
  1961. }
  1962. peer_termination_type = ConsoleServerPort
  1963. user_permissions = ('dcim.view_device', )
  1964. @classmethod
  1965. def setUpTestData(cls):
  1966. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  1967. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1968. site = Site.objects.create(name='Site 1', slug='site-1')
  1969. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  1970. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  1971. console_ports = (
  1972. ConsolePort(device=device, name='Console Port 1'),
  1973. ConsolePort(device=device, name='Console Port 2'),
  1974. ConsolePort(device=device, name='Console Port 3'),
  1975. )
  1976. ConsolePort.objects.bulk_create(console_ports)
  1977. cls.create_data = [
  1978. {
  1979. 'device': device.pk,
  1980. 'name': 'Console Port 4',
  1981. 'speed': 9600,
  1982. },
  1983. {
  1984. 'device': device.pk,
  1985. 'name': 'Console Port 5',
  1986. 'speed': 115200,
  1987. },
  1988. {
  1989. 'device': device.pk,
  1990. 'name': 'Console Port 6',
  1991. 'speed': None,
  1992. },
  1993. ]
  1994. class ConsoleServerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
  1995. model = ConsoleServerPort
  1996. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  1997. bulk_update_data = {
  1998. 'description': 'New description',
  1999. }
  2000. peer_termination_type = ConsolePort
  2001. user_permissions = ('dcim.view_device', )
  2002. @classmethod
  2003. def setUpTestData(cls):
  2004. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2005. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2006. site = Site.objects.create(name='Site 1', slug='site-1')
  2007. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2008. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2009. console_server_ports = (
  2010. ConsoleServerPort(device=device, name='Console Server Port 1'),
  2011. ConsoleServerPort(device=device, name='Console Server Port 2'),
  2012. ConsoleServerPort(device=device, name='Console Server Port 3'),
  2013. )
  2014. ConsoleServerPort.objects.bulk_create(console_server_ports)
  2015. cls.create_data = [
  2016. {
  2017. 'device': device.pk,
  2018. 'name': 'Console Server Port 4',
  2019. 'speed': 9600,
  2020. },
  2021. {
  2022. 'device': device.pk,
  2023. 'name': 'Console Server Port 5',
  2024. 'speed': 115200,
  2025. },
  2026. {
  2027. 'device': device.pk,
  2028. 'name': 'Console Server Port 6',
  2029. 'speed': None,
  2030. },
  2031. ]
  2032. class PowerPortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
  2033. model = PowerPort
  2034. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  2035. bulk_update_data = {
  2036. 'description': 'New description',
  2037. }
  2038. peer_termination_type = PowerOutlet
  2039. user_permissions = ('dcim.view_device', )
  2040. @classmethod
  2041. def setUpTestData(cls):
  2042. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2043. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2044. site = Site.objects.create(name='Site 1', slug='site-1')
  2045. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2046. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2047. power_ports = (
  2048. PowerPort(device=device, name='Power Port 1'),
  2049. PowerPort(device=device, name='Power Port 2'),
  2050. PowerPort(device=device, name='Power Port 3'),
  2051. )
  2052. PowerPort.objects.bulk_create(power_ports)
  2053. cls.create_data = [
  2054. {
  2055. 'device': device.pk,
  2056. 'name': 'Power Port 4',
  2057. },
  2058. {
  2059. 'device': device.pk,
  2060. 'name': 'Power Port 5',
  2061. },
  2062. {
  2063. 'device': device.pk,
  2064. 'name': 'Power Port 6',
  2065. },
  2066. ]
  2067. class PowerOutletTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
  2068. model = PowerOutlet
  2069. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  2070. bulk_update_data = {
  2071. 'description': 'New description',
  2072. }
  2073. peer_termination_type = PowerPort
  2074. user_permissions = ('dcim.view_device', )
  2075. @classmethod
  2076. def setUpTestData(cls):
  2077. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2078. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2079. site = Site.objects.create(name='Site 1', slug='site-1')
  2080. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2081. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2082. power_ports = (
  2083. PowerPort(device=device, name='Power Port 1'),
  2084. PowerPort(device=device, name='Power Port 2'),
  2085. )
  2086. PowerPort.objects.bulk_create(power_ports)
  2087. power_outlets = (
  2088. PowerOutlet(device=device, name='Power Outlet 1'),
  2089. PowerOutlet(device=device, name='Power Outlet 2'),
  2090. PowerOutlet(device=device, name='Power Outlet 3'),
  2091. )
  2092. PowerOutlet.objects.bulk_create(power_outlets)
  2093. cls.create_data = [
  2094. {
  2095. 'device': device.pk,
  2096. 'name': 'Power Outlet 4',
  2097. 'power_port': power_ports[0].pk,
  2098. },
  2099. {
  2100. 'device': device.pk,
  2101. 'name': 'Power Outlet 5',
  2102. 'power_port': power_ports[1].pk,
  2103. },
  2104. {
  2105. 'device': device.pk,
  2106. 'name': 'Power Outlet 6',
  2107. 'power_port': None,
  2108. },
  2109. ]
  2110. class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
  2111. model = Interface
  2112. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  2113. bulk_update_data = {
  2114. 'description': 'New description',
  2115. }
  2116. peer_termination_type = Interface
  2117. user_permissions = ('dcim.view_device', )
  2118. @classmethod
  2119. def setUpTestData(cls):
  2120. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2121. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2122. site = Site.objects.create(name='Site 1', slug='site-1')
  2123. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2124. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2125. interfaces = (
  2126. Interface(device=device, name='Interface 1', type='1000base-t'),
  2127. Interface(device=device, name='Interface 2', type='1000base-t'),
  2128. Interface(device=device, name='Interface 3', type='1000base-t'),
  2129. )
  2130. Interface.objects.bulk_create(interfaces)
  2131. vdcs = (
  2132. VirtualDeviceContext(name='VDC 1', identifier=1, device=device),
  2133. VirtualDeviceContext(name='VDC 2', identifier=2, device=device)
  2134. )
  2135. VirtualDeviceContext.objects.bulk_create(vdcs)
  2136. vlans = (
  2137. VLAN(name='VLAN 1', vid=1),
  2138. VLAN(name='VLAN 2', vid=2),
  2139. VLAN(name='VLAN 3', vid=3),
  2140. VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
  2141. )
  2142. VLAN.objects.bulk_create(vlans)
  2143. wireless_lans = (
  2144. WirelessLAN(ssid='WLAN1'),
  2145. WirelessLAN(ssid='WLAN2'),
  2146. )
  2147. WirelessLAN.objects.bulk_create(wireless_lans)
  2148. vrfs = (
  2149. VRF(name='VRF 1'),
  2150. VRF(name='VRF 2'),
  2151. VRF(name='VRF 3'),
  2152. )
  2153. VRF.objects.bulk_create(vrfs)
  2154. cls.create_data = [
  2155. {
  2156. 'device': device.pk,
  2157. 'name': 'Interface 4',
  2158. 'type': '1000base-t',
  2159. 'mode': InterfaceModeChoices.MODE_TAGGED,
  2160. 'speed': 1000000,
  2161. 'duplex': 'full',
  2162. 'vrf': vrfs[0].pk,
  2163. 'poe_mode': InterfacePoEModeChoices.MODE_PD,
  2164. 'poe_type': InterfacePoETypeChoices.TYPE_1_8023AF,
  2165. 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
  2166. 'untagged_vlan': vlans[2].pk,
  2167. },
  2168. {
  2169. 'device': device.pk,
  2170. 'name': 'Interface 5',
  2171. 'type': '1000base-t',
  2172. 'mode': InterfaceModeChoices.MODE_TAGGED,
  2173. 'bridge': interfaces[0].pk,
  2174. 'speed': 100000,
  2175. 'duplex': 'half',
  2176. 'vrf': vrfs[1].pk,
  2177. 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
  2178. 'untagged_vlan': vlans[2].pk,
  2179. },
  2180. {
  2181. 'device': device.pk,
  2182. 'vdcs': [vdcs[0].pk],
  2183. 'name': 'Interface 6',
  2184. 'type': 'virtual',
  2185. 'mode': InterfaceModeChoices.MODE_TAGGED,
  2186. 'parent': interfaces[1].pk,
  2187. 'vrf': vrfs[2].pk,
  2188. 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
  2189. 'untagged_vlan': vlans[2].pk,
  2190. },
  2191. {
  2192. 'device': device.pk,
  2193. 'vdcs': [vdcs[1].pk],
  2194. 'name': 'Interface 7',
  2195. 'type': InterfaceTypeChoices.TYPE_80211A,
  2196. 'mode': InterfaceModeChoices.MODE_Q_IN_Q,
  2197. 'tx_power': 10,
  2198. 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
  2199. 'rf_channel': WirelessChannelChoices.CHANNEL_5G_32,
  2200. 'qinq_svlan': vlans[3].pk,
  2201. },
  2202. {
  2203. 'device': device.pk,
  2204. 'vdcs': [vdcs[1].pk],
  2205. 'name': 'Interface 8',
  2206. 'type': InterfaceTypeChoices.TYPE_80211A,
  2207. 'mode': InterfaceModeChoices.MODE_Q_IN_Q,
  2208. 'tx_power': 10,
  2209. 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
  2210. 'rf_channel': "",
  2211. 'qinq_svlan': vlans[3].pk,
  2212. },
  2213. ]
  2214. def _perform_interface_test_with_invalid_data(self, mode: str = None, invalid_data: dict = {}):
  2215. device = Device.objects.first()
  2216. data = {
  2217. 'device': device.pk,
  2218. 'name': 'Interface 1',
  2219. 'type': InterfaceTypeChoices.TYPE_1GE_FIXED,
  2220. }
  2221. data.update({'mode': mode})
  2222. data.update(invalid_data)
  2223. response = self.client.post(self._get_list_url(), data, format='json', **self.header)
  2224. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  2225. content = json.loads(response.content)
  2226. for key in invalid_data.keys():
  2227. self.assertIn(key, content)
  2228. self.assertIsNone(content.get('data'))
  2229. def test_bulk_delete_child_interfaces(self):
  2230. interface1 = Interface.objects.get(name='Interface 1')
  2231. device = interface1.device
  2232. self.add_permissions('dcim.delete_interface')
  2233. # Create a child interface
  2234. child = Interface.objects.create(
  2235. device=device,
  2236. name='Interface 1A',
  2237. type=InterfaceTypeChoices.TYPE_VIRTUAL,
  2238. parent=interface1
  2239. )
  2240. self.assertEqual(device.interfaces.count(), 4)
  2241. # Attempt to delete only the parent interface
  2242. url = self._get_detail_url(interface1)
  2243. with disable_logging():
  2244. self.client.delete(url, **self.header)
  2245. self.assertEqual(device.interfaces.count(), 4) # Parent was not deleted
  2246. # Attempt to bulk delete parent & child together
  2247. data = [
  2248. {"id": interface1.pk},
  2249. {"id": child.pk},
  2250. ]
  2251. self.client.delete(self._get_list_url(), data, format='json', **self.header)
  2252. self.assertEqual(device.interfaces.count(), 2) # Child & parent were both deleted
  2253. def test_create_child_interfaces_mode_invalid_data(self):
  2254. """
  2255. POST data to test interface mode check and invalid tagged/untagged VLANS.
  2256. """
  2257. self.add_permissions('dcim.add_interface')
  2258. vlans = VLAN.objects.all()[0:3]
  2259. # Routed mode, untagged, tagged and qinq service vlan
  2260. invalid_data = {
  2261. 'untagged_vlan': vlans[0].pk,
  2262. 'tagged_vlans': [vlans[1].pk, vlans[2].pk],
  2263. 'qinq_svlan': vlans[2].pk
  2264. }
  2265. self._perform_interface_test_with_invalid_data(None, invalid_data)
  2266. # Routed mode, untagged and tagged vlan
  2267. invalid_data = {
  2268. 'untagged_vlan': vlans[0].pk,
  2269. 'tagged_vlans': [vlans[1].pk, vlans[2].pk],
  2270. }
  2271. self._perform_interface_test_with_invalid_data(None, invalid_data)
  2272. # Routed mode, untagged vlan
  2273. invalid_data = {
  2274. 'untagged_vlan': vlans[0].pk,
  2275. }
  2276. self._perform_interface_test_with_invalid_data(None, invalid_data)
  2277. invalid_data = {
  2278. 'tagged_vlans': [vlans[1].pk, vlans[2].pk],
  2279. }
  2280. # Routed mode, qinq service vlan
  2281. self._perform_interface_test_with_invalid_data(None, invalid_data)
  2282. # Access mode, tagged vlans
  2283. self._perform_interface_test_with_invalid_data(InterfaceModeChoices.MODE_ACCESS, invalid_data)
  2284. # All tagged mode, tagged vlans
  2285. self._perform_interface_test_with_invalid_data(InterfaceModeChoices.MODE_TAGGED_ALL, invalid_data)
  2286. invalid_data = {
  2287. 'qinq_svlan': vlans[0].pk,
  2288. }
  2289. # Routed mode, qinq service vlan
  2290. self._perform_interface_test_with_invalid_data(None, invalid_data)
  2291. # Access mode, qinq service vlan
  2292. self._perform_interface_test_with_invalid_data(InterfaceModeChoices.MODE_ACCESS, invalid_data)
  2293. # Tagged mode, qinq service vlan
  2294. self._perform_interface_test_with_invalid_data(InterfaceModeChoices.MODE_TAGGED, invalid_data)
  2295. # Tagged-all mode, qinq service vlan
  2296. self._perform_interface_test_with_invalid_data(InterfaceModeChoices.MODE_TAGGED_ALL, invalid_data)
  2297. class FrontPortTest(APIViewTestCases.APIViewTestCase):
  2298. model = FrontPort
  2299. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  2300. bulk_update_data = {
  2301. 'description': 'New description',
  2302. }
  2303. peer_termination_type = Interface
  2304. user_permissions = ('dcim.view_device', 'dcim.view_rearport')
  2305. @classmethod
  2306. def setUpTestData(cls):
  2307. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2308. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2309. site = Site.objects.create(name='Site 1', slug='site-1')
  2310. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2311. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2312. rear_ports = (
  2313. RearPort(device=device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
  2314. RearPort(device=device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
  2315. RearPort(device=device, name='Rear Port 3', type=PortTypeChoices.TYPE_8P8C),
  2316. RearPort(device=device, name='Rear Port 4', type=PortTypeChoices.TYPE_8P8C),
  2317. RearPort(device=device, name='Rear Port 5', type=PortTypeChoices.TYPE_8P8C),
  2318. RearPort(device=device, name='Rear Port 6', type=PortTypeChoices.TYPE_8P8C),
  2319. )
  2320. RearPort.objects.bulk_create(rear_ports)
  2321. front_ports = (
  2322. FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
  2323. FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
  2324. FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C),
  2325. )
  2326. FrontPort.objects.bulk_create(front_ports)
  2327. PortMapping.objects.bulk_create([
  2328. PortMapping(device=device, front_port=front_ports[0], rear_port=rear_ports[0]),
  2329. PortMapping(device=device, front_port=front_ports[1], rear_port=rear_ports[1]),
  2330. PortMapping(device=device, front_port=front_ports[2], rear_port=rear_ports[2]),
  2331. ])
  2332. cls.create_data = [
  2333. {
  2334. 'device': device.pk,
  2335. 'name': 'Front Port 4',
  2336. 'type': PortTypeChoices.TYPE_8P8C,
  2337. 'rear_ports': [
  2338. {
  2339. 'position': 1,
  2340. 'rear_port': rear_ports[3].pk,
  2341. 'rear_port_position': 1,
  2342. },
  2343. ],
  2344. },
  2345. {
  2346. 'device': device.pk,
  2347. 'name': 'Front Port 5',
  2348. 'type': PortTypeChoices.TYPE_8P8C,
  2349. 'rear_ports': [
  2350. {
  2351. 'position': 1,
  2352. 'rear_port': rear_ports[4].pk,
  2353. 'rear_port_position': 1,
  2354. },
  2355. ],
  2356. },
  2357. {
  2358. 'device': device.pk,
  2359. 'name': 'Front Port 6',
  2360. 'type': PortTypeChoices.TYPE_8P8C,
  2361. 'rear_ports': [
  2362. {
  2363. 'position': 1,
  2364. 'rear_port': rear_ports[5].pk,
  2365. 'rear_port_position': 1,
  2366. },
  2367. ],
  2368. },
  2369. ]
  2370. cls.update_data = {
  2371. 'type': PortTypeChoices.TYPE_LC,
  2372. 'rear_ports': [
  2373. {
  2374. 'position': 1,
  2375. 'rear_port': rear_ports[3].pk,
  2376. 'rear_port_position': 1,
  2377. },
  2378. ],
  2379. }
  2380. def test_update_object(self):
  2381. super().test_update_object()
  2382. # Check that the port mapping was updated after modifying the front port
  2383. front_port = FrontPort.objects.get(name='Front Port 1')
  2384. rear_port = RearPort.objects.get(name='Rear Port 4')
  2385. self.assertTrue(
  2386. PortMapping.objects.filter(
  2387. front_port=front_port,
  2388. front_port_position=1,
  2389. rear_port=rear_port,
  2390. rear_port_position=1,
  2391. ).exists()
  2392. )
  2393. @tag('regression') # Issue #18991
  2394. def test_front_port_paths(self):
  2395. device = Device.objects.first()
  2396. interface1 = Interface.objects.create(device=device, name='Interface 1')
  2397. rear_port = RearPort.objects.create(device=device, name='Rear Port 10', type=PortTypeChoices.TYPE_8P8C)
  2398. front_port = FrontPort.objects.create(device=device, name='Front Port 10', type=PortTypeChoices.TYPE_8P8C)
  2399. PortMapping.objects.create(device=device, front_port=front_port, rear_port=rear_port)
  2400. Cable.objects.create(a_terminations=[interface1], b_terminations=[front_port])
  2401. self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
  2402. url = reverse(f'dcim-api:{self.model._meta.model_name}-paths', kwargs={'pk': front_port.pk})
  2403. response = self.client.get(url, **self.header)
  2404. self.assertHttpStatus(response, status.HTTP_200_OK)
  2405. class RearPortTest(APIViewTestCases.APIViewTestCase):
  2406. model = RearPort
  2407. brief_fields = ['_occupied', 'cable', 'description', 'device', 'display', 'id', 'name', 'url']
  2408. bulk_update_data = {
  2409. 'description': 'New description',
  2410. }
  2411. peer_termination_type = Interface
  2412. user_permissions = ('dcim.view_device', )
  2413. @classmethod
  2414. def setUpTestData(cls):
  2415. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2416. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2417. site = Site.objects.create(name='Site 1', slug='site-1')
  2418. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2419. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2420. front_ports = (
  2421. FrontPort(device=device, name='Front Port 1', type=PortTypeChoices.TYPE_8P8C),
  2422. FrontPort(device=device, name='Front Port 2', type=PortTypeChoices.TYPE_8P8C),
  2423. FrontPort(device=device, name='Front Port 3', type=PortTypeChoices.TYPE_8P8C),
  2424. FrontPort(device=device, name='Front Port 4', type=PortTypeChoices.TYPE_8P8C),
  2425. FrontPort(device=device, name='Front Port 5', type=PortTypeChoices.TYPE_8P8C),
  2426. FrontPort(device=device, name='Front Port 6', type=PortTypeChoices.TYPE_8P8C),
  2427. )
  2428. FrontPort.objects.bulk_create(front_ports)
  2429. rear_ports = (
  2430. RearPort(device=device, name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C),
  2431. RearPort(device=device, name='Rear Port 2', type=PortTypeChoices.TYPE_8P8C),
  2432. RearPort(device=device, name='Rear Port 3', type=PortTypeChoices.TYPE_8P8C),
  2433. )
  2434. RearPort.objects.bulk_create(rear_ports)
  2435. cls.create_data = [
  2436. {
  2437. 'device': device.pk,
  2438. 'name': 'Rear Port 4',
  2439. 'type': PortTypeChoices.TYPE_8P8C,
  2440. 'front_ports': [
  2441. {
  2442. 'position': 1,
  2443. 'front_port': front_ports[3].pk,
  2444. 'front_port_position': 1,
  2445. },
  2446. ],
  2447. },
  2448. {
  2449. 'device': device.pk,
  2450. 'name': 'Rear Port 5',
  2451. 'type': PortTypeChoices.TYPE_8P8C,
  2452. 'front_ports': [
  2453. {
  2454. 'position': 1,
  2455. 'front_port': front_ports[4].pk,
  2456. 'front_port_position': 1,
  2457. },
  2458. ],
  2459. },
  2460. {
  2461. 'device': device.pk,
  2462. 'name': 'Rear Port 6',
  2463. 'type': PortTypeChoices.TYPE_8P8C,
  2464. 'front_ports': [
  2465. {
  2466. 'position': 1,
  2467. 'front_port': front_ports[5].pk,
  2468. 'front_port_position': 1,
  2469. },
  2470. ],
  2471. },
  2472. ]
  2473. cls.update_data = {
  2474. 'type': PortTypeChoices.TYPE_LC,
  2475. 'front_ports': [
  2476. {
  2477. 'position': 1,
  2478. 'front_port': front_ports[3].pk,
  2479. 'front_port_position': 1,
  2480. },
  2481. ],
  2482. }
  2483. def test_update_object(self):
  2484. super().test_update_object()
  2485. # Check that the port mapping was updated after modifying the rear port
  2486. front_port = FrontPort.objects.get(name='Front Port 4')
  2487. rear_port = RearPort.objects.get(name='Rear Port 1')
  2488. self.assertTrue(
  2489. PortMapping.objects.filter(
  2490. front_port=front_port,
  2491. front_port_position=1,
  2492. rear_port=rear_port,
  2493. rear_port_position=1,
  2494. ).exists()
  2495. )
  2496. @tag('regression') # Issue #18991
  2497. def test_rear_port_paths(self):
  2498. device = Device.objects.first()
  2499. interface1 = Interface.objects.create(device=device, name='Interface 1')
  2500. rear_port = RearPort.objects.create(
  2501. device=device,
  2502. name='Rear Port 10',
  2503. type=PortTypeChoices.TYPE_8P8C,
  2504. )
  2505. Cable.objects.create(a_terminations=[interface1], b_terminations=[rear_port])
  2506. self.add_permissions(f'dcim.view_{self.model._meta.model_name}')
  2507. url = reverse(f'dcim-api:{self.model._meta.model_name}-paths', kwargs={'pk': rear_port.pk})
  2508. response = self.client.get(url, **self.header)
  2509. self.assertHttpStatus(response, status.HTTP_200_OK)
  2510. class ModuleBayTest(APIViewTestCases.APIViewTestCase):
  2511. model = ModuleBay
  2512. brief_fields = ['_occupied', 'description', 'display', 'enabled', 'id', 'installed_module', 'name', 'url']
  2513. bulk_update_data = {
  2514. 'description': 'New description',
  2515. }
  2516. user_permissions = ('dcim.view_device', )
  2517. @classmethod
  2518. def setUpTestData(cls):
  2519. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2520. site = Site.objects.create(name='Site 1', slug='site-1')
  2521. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2522. device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2523. device = Device.objects.create(device_type=device_type, role=role, name='Device 1', site=site)
  2524. module_bays = (
  2525. ModuleBay(device=device, name='Device Bay 1', enabled=True),
  2526. ModuleBay(device=device, name='Device Bay 2', enabled=False),
  2527. ModuleBay(device=device, name='Device Bay 3', enabled=True),
  2528. )
  2529. for module_bay in module_bays:
  2530. module_bay.save()
  2531. cls.create_data = [
  2532. {
  2533. 'device': device.pk,
  2534. 'name': 'Device Bay 4',
  2535. 'enabled': False,
  2536. },
  2537. {
  2538. 'device': device.pk,
  2539. 'name': 'Device Bay 5',
  2540. },
  2541. {
  2542. 'device': device.pk,
  2543. 'name': 'Device Bay 6',
  2544. },
  2545. ]
  2546. class DeviceBayTest(APIViewTestCases.APIViewTestCase):
  2547. model = DeviceBay
  2548. brief_fields = ['_occupied', 'description', 'device', 'display', 'enabled', 'id', 'name', 'url']
  2549. bulk_update_data = {
  2550. 'description': 'New description',
  2551. }
  2552. user_permissions = ('dcim.view_device', )
  2553. @classmethod
  2554. def setUpTestData(cls):
  2555. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2556. site = Site.objects.create(name='Site 1', slug='site-1')
  2557. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2558. device_types = (
  2559. DeviceType(
  2560. manufacturer=manufacturer,
  2561. model='Device Type 1',
  2562. slug='device-type-1',
  2563. subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
  2564. ),
  2565. DeviceType(
  2566. manufacturer=manufacturer,
  2567. model='Device Type 2',
  2568. slug='device-type-2',
  2569. subdevice_role=SubdeviceRoleChoices.ROLE_CHILD
  2570. ),
  2571. )
  2572. DeviceType.objects.bulk_create(device_types)
  2573. devices = (
  2574. Device(device_type=device_types[0], role=role, name='Device 1', site=site),
  2575. Device(device_type=device_types[1], role=role, name='Device 2', site=site),
  2576. Device(device_type=device_types[1], role=role, name='Device 3', site=site),
  2577. Device(device_type=device_types[1], role=role, name='Device 4', site=site),
  2578. )
  2579. Device.objects.bulk_create(devices)
  2580. device_bays = (
  2581. DeviceBay(device=devices[0], name='Device Bay 1', enabled=True),
  2582. DeviceBay(device=devices[0], name='Device Bay 2', enabled=False),
  2583. DeviceBay(device=devices[0], name='Device Bay 3', enabled=True),
  2584. )
  2585. DeviceBay.objects.bulk_create(device_bays)
  2586. cls.create_data = [
  2587. {
  2588. 'device': devices[0].pk,
  2589. 'name': 'Device Bay 4',
  2590. 'installed_device': devices[1].pk,
  2591. },
  2592. {
  2593. 'device': devices[0].pk,
  2594. 'name': 'Device Bay 5',
  2595. 'installed_device': devices[2].pk,
  2596. },
  2597. {
  2598. 'device': devices[0].pk,
  2599. 'name': 'Device Bay 6',
  2600. 'installed_device': devices[3].pk,
  2601. },
  2602. ]
  2603. class InventoryItemTest(APIViewTestCases.APIViewTestCase):
  2604. model = InventoryItem
  2605. brief_fields = ['_depth', 'description', 'device', 'display', 'id', 'name', 'url']
  2606. bulk_update_data = {
  2607. 'description': 'New description',
  2608. }
  2609. user_permissions = ('dcim.view_device', 'dcim.view_manufacturer')
  2610. @classmethod
  2611. def setUpTestData(cls):
  2612. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  2613. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2614. site = Site.objects.create(name='Site 1', slug='site-1')
  2615. role = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1', color='ff0000')
  2616. device = Device.objects.create(device_type=devicetype, role=role, name='Device 1', site=site)
  2617. roles = (
  2618. InventoryItemRole(name='Inventory Item Role 1', slug='inventory-item-role-1'),
  2619. InventoryItemRole(name='Inventory Item Role 2', slug='inventory-item-role-2'),
  2620. )
  2621. InventoryItemRole.objects.bulk_create(roles)
  2622. interfaces = (
  2623. Interface(device=device, name='Interface 1'),
  2624. Interface(device=device, name='Interface 2'),
  2625. Interface(device=device, name='Interface 3'),
  2626. )
  2627. Interface.objects.bulk_create(interfaces)
  2628. InventoryItem.objects.create(
  2629. device=device, name='Inventory Item 1', role=roles[0], manufacturer=manufacturer, component=interfaces[0]
  2630. )
  2631. InventoryItem.objects.create(
  2632. device=device, name='Inventory Item 2', role=roles[0], manufacturer=manufacturer, component=interfaces[1]
  2633. )
  2634. InventoryItem.objects.create(
  2635. device=device, name='Inventory Item 3', role=roles[0], manufacturer=manufacturer, component=interfaces[2]
  2636. )
  2637. cls.create_data = [
  2638. {
  2639. 'device': device.pk,
  2640. 'name': 'Inventory Item 4',
  2641. 'role': roles[1].pk,
  2642. 'manufacturer': manufacturer.pk,
  2643. 'component_type': 'dcim.interface',
  2644. 'component_id': interfaces[0].pk,
  2645. },
  2646. {
  2647. 'device': device.pk,
  2648. 'name': 'Inventory Item 5',
  2649. 'role': roles[1].pk,
  2650. 'manufacturer': manufacturer.pk,
  2651. 'component_type': 'dcim.interface',
  2652. 'component_id': interfaces[1].pk,
  2653. },
  2654. {
  2655. 'device': device.pk,
  2656. 'name': 'Inventory Item 6',
  2657. 'role': roles[1].pk,
  2658. 'manufacturer': manufacturer.pk,
  2659. 'component_type': 'dcim.interface',
  2660. 'component_id': interfaces[2].pk,
  2661. },
  2662. ]
  2663. class InventoryItemRoleTest(APIViewTestCases.APIViewTestCase):
  2664. model = InventoryItemRole
  2665. brief_fields = ['description', 'display', 'id', 'inventoryitem_count', 'name', 'slug', 'url']
  2666. create_data = [
  2667. {
  2668. 'name': 'Inventory Item Role 4',
  2669. 'slug': 'inventory-item-role-4',
  2670. 'color': 'ffff00',
  2671. },
  2672. {
  2673. 'name': 'Inventory Item Role 5',
  2674. 'slug': 'inventory-item-role-5',
  2675. 'color': 'ffff00',
  2676. },
  2677. {
  2678. 'name': 'Inventory Item Role 6',
  2679. 'slug': 'inventory-item-role-6',
  2680. 'color': 'ffff00',
  2681. },
  2682. ]
  2683. bulk_update_data = {
  2684. 'description': 'New description',
  2685. }
  2686. @classmethod
  2687. def setUpTestData(cls):
  2688. roles = (
  2689. InventoryItemRole(name='Inventory Item Role 1', slug='inventory-item-role-1', color='ff0000'),
  2690. InventoryItemRole(name='Inventory Item Role 2', slug='inventory-item-role-2', color='00ff00'),
  2691. InventoryItemRole(name='Inventory Item Role 3', slug='inventory-item-role-3', color='0000ff'),
  2692. )
  2693. InventoryItemRole.objects.bulk_create(roles)
  2694. class CableBundleTest(APIViewTestCases.APIViewTestCase):
  2695. model = CableBundle
  2696. brief_fields = ['description', 'display', 'id', 'name', 'url']
  2697. create_data = [
  2698. {'name': 'Cable Bundle 4'},
  2699. {'name': 'Cable Bundle 5'},
  2700. {'name': 'Cable Bundle 6'},
  2701. ]
  2702. bulk_update_data = {
  2703. 'description': 'New description',
  2704. }
  2705. @classmethod
  2706. def setUpTestData(cls):
  2707. cable_bundles = (
  2708. CableBundle(name='Cable Bundle 1'),
  2709. CableBundle(name='Cable Bundle 2'),
  2710. CableBundle(name='Cable Bundle 3'),
  2711. )
  2712. CableBundle.objects.bulk_create(cable_bundles)
  2713. def test_cable_count(self):
  2714. """cable_count annotation is returned correctly in the API response."""
  2715. self.add_permissions('dcim.view_cablebundle')
  2716. bundle = CableBundle.objects.first()
  2717. site = Site.objects.create(name='CB Test Site', slug='cb-test-site')
  2718. manufacturer = Manufacturer.objects.create(name='CB Manufacturer', slug='cb-manufacturer')
  2719. device_type = DeviceType.objects.create(
  2720. manufacturer=manufacturer, model='CB Device Type', slug='cb-device-type'
  2721. )
  2722. role = DeviceRole.objects.create(name='CB Role', slug='cb-role', color='ff0000')
  2723. devices = (
  2724. Device(device_type=device_type, role=role, name='CB Device 1', site=site),
  2725. Device(device_type=device_type, role=role, name='CB Device 2', site=site),
  2726. )
  2727. Device.objects.bulk_create(devices)
  2728. interfaces = (
  2729. Interface(device=devices[0], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2730. Interface(device=devices[0], name='eth1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2731. Interface(device=devices[1], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2732. Interface(device=devices[1], name='eth1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2733. )
  2734. Interface.objects.bulk_create(interfaces)
  2735. for a, b in [(interfaces[0], interfaces[2]), (interfaces[1], interfaces[3])]:
  2736. cable = Cable(a_terminations=[a], b_terminations=[b], bundle=bundle)
  2737. cable.save()
  2738. url = reverse('dcim-api:cablebundle-detail', kwargs={'pk': bundle.pk})
  2739. response = self.client.get(url, **self.header)
  2740. self.assertHttpStatus(response, status.HTTP_200_OK)
  2741. self.assertEqual(response.data['cable_count'], 2)
  2742. class CableTest(APIViewTestCases.APIViewTestCase):
  2743. model = Cable
  2744. brief_fields = ['description', 'display', 'id', 'label', 'url']
  2745. bulk_update_data = {
  2746. 'length': 100,
  2747. 'length_unit': 'm',
  2748. }
  2749. # TODO: Allow updating cable terminations
  2750. test_update_object = None
  2751. def model_to_dict(self, *args, **kwargs):
  2752. data = super().model_to_dict(*args, **kwargs)
  2753. # Serialize termination objects
  2754. if 'a_terminations' in data:
  2755. data['a_terminations'] = GenericObjectSerializer(data['a_terminations'], many=True).data
  2756. if 'b_terminations' in data:
  2757. data['b_terminations'] = GenericObjectSerializer(data['b_terminations'], many=True).data
  2758. return data
  2759. @classmethod
  2760. def setUpTestData(cls):
  2761. site = Site.objects.create(name='Site 1', slug='site-1')
  2762. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  2763. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2764. role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1', color='ff0000')
  2765. devices = (
  2766. Device(device_type=devicetype, role=role, name='Device 1', site=site),
  2767. Device(device_type=devicetype, role=role, name='Device 2', site=site),
  2768. )
  2769. Device.objects.bulk_create(devices)
  2770. interfaces = []
  2771. for device in devices:
  2772. for i in range(0, 10):
  2773. interfaces.append(Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name=f'eth{i}'))
  2774. Interface.objects.bulk_create(interfaces)
  2775. cables = (
  2776. Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[10]], label='Cable 1'),
  2777. Cable(a_terminations=[interfaces[1]], b_terminations=[interfaces[11]], label='Cable 2'),
  2778. Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[12]], label='Cable 3'),
  2779. )
  2780. for cable in cables:
  2781. cable.save()
  2782. cls.create_data = [
  2783. {
  2784. 'a_terminations': [{
  2785. 'object_type': 'dcim.interface',
  2786. 'object_id': interfaces[4].pk,
  2787. }],
  2788. 'b_terminations': [{
  2789. 'object_type': 'dcim.interface',
  2790. 'object_id': interfaces[14].pk,
  2791. }],
  2792. 'label': 'Cable 4',
  2793. 'profile': CableProfileChoices.SINGLE_1C1P,
  2794. },
  2795. {
  2796. 'a_terminations': [{
  2797. 'object_type': 'dcim.interface',
  2798. 'object_id': interfaces[5].pk,
  2799. }],
  2800. 'b_terminations': [{
  2801. 'object_type': 'dcim.interface',
  2802. 'object_id': interfaces[15].pk,
  2803. }],
  2804. 'label': 'Cable 5',
  2805. 'profile': CableProfileChoices.SINGLE_1C1P,
  2806. },
  2807. {
  2808. 'a_terminations': [{
  2809. 'object_type': 'dcim.interface',
  2810. 'object_id': interfaces[6].pk,
  2811. }],
  2812. 'b_terminations': [{
  2813. 'object_type': 'dcim.interface',
  2814. 'object_id': interfaces[16].pk,
  2815. }],
  2816. 'label': 'Cable 6',
  2817. # No profile (legacy behavior)
  2818. },
  2819. ]
  2820. def test_graphql_cable_termination_cached_filters(self):
  2821. """
  2822. Validate filtering cables by cached CableTermination relations via GraphQL:
  2823. cable_list(filters: { terminations: { <relation>: {...}, DISTINCT: true } })
  2824. Also asserts deduplication when both ends match (cable between two interfaces
  2825. on the same device/rack/location/site).
  2826. """
  2827. self.add_permissions(
  2828. 'dcim.view_cable',
  2829. 'dcim.view_device',
  2830. 'dcim.view_interface',
  2831. 'dcim.view_rack',
  2832. 'dcim.view_location',
  2833. 'dcim.view_site',
  2834. )
  2835. # Reuse existing fixtures from setUpTestData()
  2836. devicetype = DeviceType.objects.get(slug='device-type-1')
  2837. role = DeviceRole.objects.get(slug='device-role-1')
  2838. # Create an isolated topology for this test
  2839. site_a = Site.objects.create(name='GQL Site A', slug='gql-site-a')
  2840. site_b = Site.objects.create(name='GQL Site B', slug='gql-site-b')
  2841. location_a = Location.objects.create(
  2842. site=site_a,
  2843. name='GQL Location A',
  2844. slug='gql-location-a',
  2845. status=LocationStatusChoices.STATUS_ACTIVE,
  2846. )
  2847. location_b = Location.objects.create(
  2848. site=site_b,
  2849. name='GQL Location B',
  2850. slug='gql-location-b',
  2851. status=LocationStatusChoices.STATUS_ACTIVE,
  2852. )
  2853. rack_a = Rack.objects.create(site=site_a, location=location_a, name='GQL Rack A', u_height=42)
  2854. rack_b = Rack.objects.create(site=site_b, location=location_b, name='GQL Rack B', u_height=42)
  2855. device_a = Device.objects.create(
  2856. device_type=devicetype,
  2857. role=role,
  2858. name='GQL Device A',
  2859. site=site_a,
  2860. location=location_a,
  2861. rack=rack_a,
  2862. )
  2863. device_b = Device.objects.create(
  2864. device_type=devicetype,
  2865. role=role,
  2866. name='GQL Device B',
  2867. site=site_b,
  2868. location=location_b,
  2869. rack=rack_b,
  2870. )
  2871. a0 = Interface.objects.create(device=device_a, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth0')
  2872. a1 = Interface.objects.create(device=device_a, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth1')
  2873. a2 = Interface.objects.create(device=device_a, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth2')
  2874. b0 = Interface.objects.create(device=device_b, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name='eth0')
  2875. # Both ends on Device A (duplication risk without DISTINCT)
  2876. cable_same_device = Cable(a_terminations=[a0], b_terminations=[a1], label='GQL Cable Same Device')
  2877. cable_same_device.save()
  2878. # Cross to Device B
  2879. cable_cross = Cable(a_terminations=[a2], b_terminations=[b0], label='GQL Cable Cross')
  2880. cable_cross.save()
  2881. expected_a = {str(cable_same_device.pk), str(cable_cross.pk)}
  2882. expected_b = {str(cable_cross.pk)}
  2883. url = reverse('graphql')
  2884. test_cases = (
  2885. # Device (ID + name)
  2886. (f'device: {{ id: {{ exact: "{device_a.pk}" }} }}', expected_a),
  2887. (f'device: {{ name: {{ exact: "{device_a.name}" }} }}', expected_a),
  2888. (f'device: {{ id: {{ exact: "{device_b.pk}" }} }}', expected_b),
  2889. (f'device: {{ name: {{ exact: "{device_b.name}" }} }}', expected_b),
  2890. # Rack (ID + name)
  2891. (f'rack: {{ id: {{ exact: "{rack_a.pk}" }} }}', expected_a),
  2892. (f'rack: {{ name: {{ exact: "{rack_a.name}" }} }}', expected_a),
  2893. (f'rack: {{ id: {{ exact: "{rack_b.pk}" }} }}', expected_b),
  2894. (f'rack: {{ name: {{ exact: "{rack_b.name}" }} }}', expected_b),
  2895. # Location (ID + name)
  2896. (f'location: {{ id: {{ exact: "{location_a.pk}" }} }}', expected_a),
  2897. (f'location: {{ name: {{ exact: "{location_a.name}" }} }}', expected_a),
  2898. (f'location: {{ id: {{ exact: "{location_b.pk}" }} }}', expected_b),
  2899. (f'location: {{ name: {{ exact: "{location_b.name}" }} }}', expected_b),
  2900. # Site (ID + slug)
  2901. (f'site: {{ id: {{ exact: "{site_a.pk}" }} }}', expected_a),
  2902. (f'site: {{ slug: {{ exact: "{site_a.slug}" }} }}', expected_a),
  2903. (f'site: {{ id: {{ exact: "{site_b.pk}" }} }}', expected_b),
  2904. (f'site: {{ slug: {{ exact: "{site_b.slug}" }} }}', expected_b),
  2905. )
  2906. for inner_filter, expected in test_cases:
  2907. with self.subTest(filter=inner_filter):
  2908. query = f"""{{
  2909. cable_list(filters: {{ terminations: {{ {inner_filter} DISTINCT: true }} }})
  2910. {{ id }}
  2911. }}"""
  2912. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  2913. self.assertHttpStatus(response, status.HTTP_200_OK)
  2914. data = response.json()
  2915. self.assertNotIn('errors', data)
  2916. rows = data['data']['cable_list']
  2917. ids = [row['id'] for row in rows]
  2918. # Ensure DISTINCT is actually effective (no duplicate cables when both ends match)
  2919. self.assertEqual(len(ids), len(set(ids)), f'Duplicate cables returned for: {inner_filter}')
  2920. self.assertSetEqual(set(ids), expected)
  2921. class CableTerminationTest(
  2922. APIViewTestCases.GetObjectViewTestCase,
  2923. APIViewTestCases.ListObjectsViewTestCase,
  2924. ):
  2925. model = CableTermination
  2926. brief_fields = [
  2927. 'cable', 'cable_end', 'connector', 'display', 'id', 'positions', 'termination_id', 'termination_type', 'url',
  2928. ]
  2929. @classmethod
  2930. def setUpTestData(cls):
  2931. device1 = create_test_device('Device 1')
  2932. device2 = create_test_device('Device 2')
  2933. interfaces = []
  2934. for device in (device1, device2):
  2935. for i in range(0, 10):
  2936. interfaces.append(Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name=f'eth{i}'))
  2937. Interface.objects.bulk_create(interfaces)
  2938. cables = (
  2939. Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[10]], label='Cable 1'),
  2940. Cable(a_terminations=[interfaces[1]], b_terminations=[interfaces[11]], label='Cable 2'),
  2941. Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[12]], label='Cable 3'),
  2942. )
  2943. for cable in cables:
  2944. cable.save()
  2945. class ConnectedDeviceTest(APITestCase):
  2946. @classmethod
  2947. def setUpTestData(cls):
  2948. site = Site.objects.create(name='Site 1', slug='site-1')
  2949. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  2950. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  2951. role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1', color='ff0000')
  2952. devices = (
  2953. Device(device_type=devicetype, role=role, name='TestDevice1', site=site),
  2954. Device(device_type=devicetype, role=role, name='TestDevice2', site=site),
  2955. )
  2956. Device.objects.bulk_create(devices)
  2957. interfaces = (
  2958. Interface(device=devices[0], name='eth0'),
  2959. Interface(device=devices[1], name='eth0'),
  2960. Interface(device=devices[0], name='eth1'), # Not connected
  2961. )
  2962. Interface.objects.bulk_create(interfaces)
  2963. cable = Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[1]])
  2964. cable.save()
  2965. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2966. def test_get_connected_device(self):
  2967. url = reverse('dcim-api:connected-device-list')
  2968. url_params = '?peer_device=TestDevice1&peer_interface=eth0'
  2969. response = self.client.get(url + url_params, **self.header)
  2970. self.assertHttpStatus(response, status.HTTP_200_OK)
  2971. self.assertEqual(response.data['name'], 'TestDevice2')
  2972. url_params = '?peer_device=TestDevice1&peer_interface=eth1'
  2973. response = self.client.get(url + url_params, **self.header)
  2974. self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
  2975. class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
  2976. model = VirtualChassis
  2977. brief_fields = ['description', 'display', 'id', 'master', 'member_count', 'name', 'url']
  2978. @classmethod
  2979. def setUpTestData(cls):
  2980. site = Site.objects.create(name='Test Site', slug='test-site')
  2981. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  2982. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type', slug='device-type')
  2983. role = DeviceRole.objects.create(name='Device Role', slug='device-role', color='ff0000')
  2984. devices = (
  2985. Device(name='Device 1', device_type=devicetype, role=role, site=site),
  2986. Device(name='Device 2', device_type=devicetype, role=role, site=site),
  2987. Device(name='Device 3', device_type=devicetype, role=role, site=site),
  2988. Device(name='Device 4', device_type=devicetype, role=role, site=site),
  2989. Device(name='Device 5', device_type=devicetype, role=role, site=site),
  2990. Device(name='Device 6', device_type=devicetype, role=role, site=site),
  2991. Device(name='Device 7', device_type=devicetype, role=role, site=site),
  2992. Device(name='Device 8', device_type=devicetype, role=role, site=site),
  2993. Device(name='Device 9', device_type=devicetype, role=role, site=site),
  2994. Device(name='Device 10', device_type=devicetype, role=role, site=site),
  2995. Device(name='Device 11', device_type=devicetype, role=role, site=site),
  2996. Device(name='Device 12', device_type=devicetype, role=role, site=site),
  2997. )
  2998. Device.objects.bulk_create(devices)
  2999. # Create 12 interfaces per device
  3000. interfaces = []
  3001. for i, device in enumerate(devices):
  3002. for j in range(0, 13):
  3003. interfaces.append(
  3004. # Interface name starts with parent device's position in VC; e.g. 1/1, 1/2, 1/3...
  3005. Interface(device=device, name=f'{i % 3 + 1}/{j}', type=InterfaceTypeChoices.TYPE_1GE_FIXED)
  3006. )
  3007. Interface.objects.bulk_create(interfaces)
  3008. # Create three VirtualChassis with three members each
  3009. virtual_chassis = (
  3010. VirtualChassis(name='Virtual Chassis 1', master=devices[0], domain='domain-1'),
  3011. VirtualChassis(name='Virtual Chassis 2', master=devices[3], domain='domain-2'),
  3012. VirtualChassis(name='Virtual Chassis 3', master=devices[6], domain='domain-3'),
  3013. )
  3014. VirtualChassis.objects.bulk_create(virtual_chassis)
  3015. Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis[0], vc_position=1)
  3016. Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis[0], vc_position=2)
  3017. Device.objects.filter(pk=devices[2].pk).update(virtual_chassis=virtual_chassis[0], vc_position=3)
  3018. Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=virtual_chassis[1], vc_position=1)
  3019. Device.objects.filter(pk=devices[4].pk).update(virtual_chassis=virtual_chassis[1], vc_position=2)
  3020. Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[1], vc_position=3)
  3021. Device.objects.filter(pk=devices[6].pk).update(virtual_chassis=virtual_chassis[2], vc_position=1)
  3022. Device.objects.filter(pk=devices[7].pk).update(virtual_chassis=virtual_chassis[2], vc_position=2)
  3023. Device.objects.filter(pk=devices[8].pk).update(virtual_chassis=virtual_chassis[2], vc_position=3)
  3024. cls.update_data = {
  3025. 'name': 'Virtual Chassis X',
  3026. 'domain': 'domain-x',
  3027. 'master': devices[1].pk,
  3028. }
  3029. cls.create_data = [
  3030. {
  3031. 'name': 'Virtual Chassis 4',
  3032. 'domain': 'domain-4',
  3033. },
  3034. {
  3035. 'name': 'Virtual Chassis 5',
  3036. 'domain': 'domain-5',
  3037. },
  3038. {
  3039. 'name': 'Virtual Chassis 6',
  3040. 'domain': 'domain-6',
  3041. },
  3042. ]
  3043. cls.bulk_update_data = {
  3044. 'domain': 'newdomain',
  3045. 'master': None
  3046. }
  3047. class PowerPanelTest(APIViewTestCases.APIViewTestCase):
  3048. model = PowerPanel
  3049. brief_fields = ['description', 'display', 'id', 'name', 'powerfeed_count', 'url']
  3050. user_permissions = ('dcim.view_site', )
  3051. @classmethod
  3052. def setUpTestData(cls):
  3053. sites = (
  3054. Site.objects.create(name='Site 1', slug='site-1'),
  3055. Site.objects.create(name='Site 2', slug='site-2'),
  3056. )
  3057. locations = (
  3058. Location.objects.create(name='Location 1', slug='location-1', site=sites[0]),
  3059. Location.objects.create(name='Location 2', slug='location-2', site=sites[0]),
  3060. Location.objects.create(name='Location 3', slug='location-3', site=sites[0]),
  3061. Location.objects.create(name='Location 4', slug='location-3', site=sites[1]),
  3062. )
  3063. power_panels = (
  3064. PowerPanel(site=sites[0], location=locations[0], name='Power Panel 1'),
  3065. PowerPanel(site=sites[0], location=locations[1], name='Power Panel 2'),
  3066. PowerPanel(site=sites[0], location=locations[2], name='Power Panel 3'),
  3067. )
  3068. PowerPanel.objects.bulk_create(power_panels)
  3069. cls.create_data = [
  3070. {
  3071. 'name': 'Power Panel 4',
  3072. 'site': sites[0].pk,
  3073. 'location': locations[0].pk,
  3074. },
  3075. {
  3076. 'name': 'Power Panel 5',
  3077. 'site': sites[0].pk,
  3078. 'location': locations[1].pk,
  3079. },
  3080. {
  3081. 'name': 'Power Panel 6',
  3082. 'site': sites[0].pk,
  3083. 'location': locations[2].pk,
  3084. },
  3085. ]
  3086. cls.bulk_update_data = {
  3087. 'site': sites[1].pk,
  3088. 'location': locations[3].pk
  3089. }
  3090. class PowerFeedTest(APIViewTestCases.APIViewTestCase):
  3091. model = PowerFeed
  3092. brief_fields = ['_occupied', 'cable', 'description', 'display', 'id', 'name', 'url']
  3093. bulk_update_data = {
  3094. 'status': 'planned',
  3095. }
  3096. user_permissions = ('dcim.view_powerpanel', )
  3097. @classmethod
  3098. def setUpTestData(cls):
  3099. site = Site.objects.create(name='Site 1', slug='site-1')
  3100. location = Location.objects.create(site=site, name='Location 1', slug='location-1')
  3101. rackrole = RackRole.objects.create(name='Rack Role 1', slug='rack-role-1', color='ff0000')
  3102. racks = (
  3103. Rack(site=site, location=location, role=rackrole, name='Rack 1'),
  3104. Rack(site=site, location=location, role=rackrole, name='Rack 2'),
  3105. Rack(site=site, location=location, role=rackrole, name='Rack 3'),
  3106. Rack(site=site, location=location, role=rackrole, name='Rack 4'),
  3107. )
  3108. Rack.objects.bulk_create(racks)
  3109. power_panels = (
  3110. PowerPanel(site=site, location=location, name='Power Panel 1'),
  3111. PowerPanel(site=site, location=location, name='Power Panel 2'),
  3112. )
  3113. PowerPanel.objects.bulk_create(power_panels)
  3114. PRIMARY = PowerFeedTypeChoices.TYPE_PRIMARY
  3115. REDUNDANT = PowerFeedTypeChoices.TYPE_REDUNDANT
  3116. power_feeds = (
  3117. PowerFeed(power_panel=power_panels[0], rack=racks[0], name='Power Feed 1A', type=PRIMARY),
  3118. PowerFeed(power_panel=power_panels[1], rack=racks[0], name='Power Feed 1B', type=REDUNDANT),
  3119. PowerFeed(power_panel=power_panels[0], rack=racks[1], name='Power Feed 2A', type=PRIMARY),
  3120. PowerFeed(power_panel=power_panels[1], rack=racks[1], name='Power Feed 2B', type=REDUNDANT),
  3121. PowerFeed(power_panel=power_panels[0], rack=racks[2], name='Power Feed 3A', type=PRIMARY),
  3122. PowerFeed(power_panel=power_panels[1], rack=racks[2], name='Power Feed 3B', type=REDUNDANT),
  3123. )
  3124. PowerFeed.objects.bulk_create(power_feeds)
  3125. cls.create_data = [
  3126. {
  3127. 'name': 'Power Feed 4A',
  3128. 'power_panel': power_panels[0].pk,
  3129. 'rack': racks[3].pk,
  3130. 'type': PRIMARY,
  3131. },
  3132. {
  3133. 'name': 'Power Feed 4B',
  3134. 'power_panel': power_panels[1].pk,
  3135. 'rack': racks[3].pk,
  3136. 'type': REDUNDANT,
  3137. },
  3138. ]
  3139. class VirtualDeviceContextTest(APIViewTestCases.APIViewTestCase):
  3140. model = VirtualDeviceContext
  3141. brief_fields = ['description', 'device', 'display', 'id', 'identifier', 'name', 'url']
  3142. bulk_update_data = {
  3143. 'status': 'planned',
  3144. }
  3145. @classmethod
  3146. def setUpTestData(cls):
  3147. site = Site.objects.create(name='Test Site', slug='test-site')
  3148. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  3149. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type', slug='device-type')
  3150. role = DeviceRole.objects.create(name='Device Role', slug='device-role', color='ff0000')
  3151. devices = (
  3152. Device(name='Device 1', device_type=devicetype, role=role, site=site),
  3153. Device(name='Device 2', device_type=devicetype, role=role, site=site),
  3154. Device(name='Device 3', device_type=devicetype, role=role, site=site),
  3155. )
  3156. Device.objects.bulk_create(devices)
  3157. vdcs = (
  3158. VirtualDeviceContext(device=devices[1], name='VDC 1', identifier=1, status='active'),
  3159. VirtualDeviceContext(device=devices[1], name='VDC 2', identifier=2, status='active'),
  3160. VirtualDeviceContext(device=devices[2], name='VDC 1', identifier=1, status='active'),
  3161. VirtualDeviceContext(device=devices[2], name='VDC 2', identifier=2, status='active'),
  3162. VirtualDeviceContext(device=devices[2], name='VDC 3', identifier=3, status='active'),
  3163. VirtualDeviceContext(device=devices[2], name='VDC 4', identifier=4, status='active'),
  3164. VirtualDeviceContext(device=devices[2], name='VDC 5', identifier=5, status='active'),
  3165. )
  3166. VirtualDeviceContext.objects.bulk_create(vdcs)
  3167. cls.create_data = [
  3168. {
  3169. 'device': devices[0].pk,
  3170. 'status': 'active',
  3171. 'name': 'VDC 1',
  3172. 'identifier': 1,
  3173. },
  3174. {
  3175. 'device': devices[0].pk,
  3176. 'status': 'active',
  3177. 'name': 'VDC 2',
  3178. 'identifier': 2,
  3179. },
  3180. {
  3181. 'device': devices[1].pk,
  3182. 'status': 'active',
  3183. 'name': 'VDC 3',
  3184. # Omit identifier to test uniqueness constraint
  3185. },
  3186. ]
  3187. class MACAddressTest(APIViewTestCases.APIViewTestCase):
  3188. model = MACAddress
  3189. brief_fields = ['description', 'display', 'id', 'mac_address', 'url']
  3190. bulk_update_data = {
  3191. 'description': 'New description',
  3192. }
  3193. @classmethod
  3194. def setUpTestData(cls):
  3195. device = create_test_device(name='Device 1')
  3196. interfaces = (
  3197. Interface(device=device, name='Interface 1', type='1000base-t'),
  3198. Interface(device=device, name='Interface 2', type='1000base-t'),
  3199. Interface(device=device, name='Interface 3', type='1000base-t'),
  3200. Interface(device=device, name='Interface 4', type='1000base-t'),
  3201. Interface(device=device, name='Interface 5', type='1000base-t'),
  3202. )
  3203. Interface.objects.bulk_create(interfaces)
  3204. mac_addresses = (
  3205. MACAddress(mac_address='00:00:00:00:00:01', assigned_object=interfaces[0]),
  3206. MACAddress(mac_address='00:00:00:00:00:02', assigned_object=interfaces[1]),
  3207. MACAddress(mac_address='00:00:00:00:00:03', assigned_object=interfaces[2]),
  3208. )
  3209. MACAddress.objects.bulk_create(mac_addresses)
  3210. cls.create_data = [
  3211. {
  3212. 'mac_address': '00:00:00:00:00:04',
  3213. 'assigned_object_type': 'dcim.interface',
  3214. 'assigned_object_id': interfaces[3].pk,
  3215. },
  3216. {
  3217. 'mac_address': '00:00:00:00:00:05',
  3218. 'assigned_object_type': 'dcim.interface',
  3219. 'assigned_object_id': interfaces[4].pk,
  3220. },
  3221. {
  3222. 'mac_address': '00:00:00:00:00:06',
  3223. },
  3224. ]