test_api.py 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451
  1. import json
  2. import logging
  3. from django.test import tag
  4. from django.urls import reverse
  5. from netaddr import IPNetwork
  6. from rest_framework import status
  7. from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
  8. from ipam.choices import *
  9. from ipam.models import *
  10. from tenancy.models import Tenant
  11. from utilities.data import string_to_ranges
  12. from utilities.testing import APITestCase, APIViewTestCases, create_test_device, disable_logging
  13. class AppTest(APITestCase):
  14. def test_root(self):
  15. url = reverse('ipam-api:api-root')
  16. response = self.client.get('{}?format=api'.format(url), **self.header)
  17. self.assertEqual(response.status_code, 200)
  18. class ASNRangeTest(APIViewTestCases.APIViewTestCase):
  19. model = ASNRange
  20. brief_fields = ['description', 'display', 'id', 'name', 'url']
  21. bulk_update_data = {
  22. 'description': 'New description',
  23. }
  24. @classmethod
  25. def setUpTestData(cls):
  26. rirs = (
  27. RIR(name='RIR 1', slug='rir-1', is_private=True),
  28. RIR(name='RIR 2', slug='rir-2', is_private=True),
  29. )
  30. RIR.objects.bulk_create(rirs)
  31. tenants = (
  32. Tenant(name='Tenant 1', slug='tenant-1'),
  33. Tenant(name='Tenant 2', slug='tenant-2'),
  34. )
  35. Tenant.objects.bulk_create(tenants)
  36. asn_ranges = (
  37. ASNRange(name='ASN Range 1', slug='asn-range-1', rir=rirs[0], tenant=tenants[0], start=100, end=199),
  38. ASNRange(name='ASN Range 2', slug='asn-range-2', rir=rirs[0], tenant=tenants[0], start=200, end=299),
  39. ASNRange(name='ASN Range 3', slug='asn-range-3', rir=rirs[0], tenant=tenants[0], start=300, end=399),
  40. )
  41. ASNRange.objects.bulk_create(asn_ranges)
  42. cls.create_data = [
  43. {
  44. 'name': 'ASN Range 4',
  45. 'slug': 'asn-range-4',
  46. 'rir': rirs[1].pk,
  47. 'start': 400,
  48. 'end': 499,
  49. 'tenant': tenants[1].pk,
  50. },
  51. {
  52. 'name': 'ASN Range 5',
  53. 'slug': 'asn-range-5',
  54. 'rir': rirs[1].pk,
  55. 'start': 500,
  56. 'end': 599,
  57. 'tenant': tenants[1].pk,
  58. },
  59. {
  60. 'name': 'ASN Range 6',
  61. 'slug': 'asn-range-6',
  62. 'rir': rirs[1].pk,
  63. 'start': 600,
  64. 'end': 699,
  65. 'tenant': tenants[1].pk,
  66. },
  67. ]
  68. def test_list_available_asns(self):
  69. """
  70. Test retrieval of all available ASNs within a parent range.
  71. """
  72. rir = RIR.objects.first()
  73. asnrange = ASNRange.objects.create(name='Range 1', slug='range-1', rir=rir, start=101, end=110)
  74. url = reverse('ipam-api:asnrange-available-asns', kwargs={'pk': asnrange.pk})
  75. self.add_permissions('ipam.view_asnrange', 'ipam.view_asn')
  76. response = self.client.get(url, **self.header)
  77. self.assertHttpStatus(response, status.HTTP_200_OK)
  78. self.assertEqual(len(response.data), 10)
  79. def test_create_single_available_asn(self):
  80. """
  81. Test creation of the first available ASN within a range.
  82. """
  83. rir = RIR.objects.first()
  84. asnrange = ASNRange.objects.create(name='Range 1', slug='range-1', rir=rir, start=101, end=110)
  85. url = reverse('ipam-api:asnrange-available-asns', kwargs={'pk': asnrange.pk})
  86. self.add_permissions('ipam.view_asnrange', 'ipam.add_asn')
  87. data = {
  88. 'description': 'New ASN'
  89. }
  90. response = self.client.post(url, data, format='json', **self.header)
  91. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  92. self.assertEqual(response.data['rir']['id'], asnrange.rir.pk)
  93. self.assertEqual(response.data['description'], data['description'])
  94. def test_create_multiple_available_asns(self):
  95. """
  96. Test the creation of several available ASNs within a parent range.
  97. """
  98. rir = RIR.objects.first()
  99. asnrange = ASNRange.objects.create(name='Range 1', slug='range-1', rir=rir, start=101, end=110)
  100. url = reverse('ipam-api:asnrange-available-asns', kwargs={'pk': asnrange.pk})
  101. self.add_permissions('ipam.view_asnrange', 'ipam.add_asn')
  102. # Try to create eleven ASNs (only ten are available)
  103. data = [
  104. {'description': f'New ASN {i}'}
  105. for i in range(1, 12)
  106. ]
  107. assert len(data) == 11
  108. response = self.client.post(url, data, format='json', **self.header)
  109. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  110. self.assertIn('detail', response.data)
  111. # Create all ten available ASNs in a single request
  112. data.pop()
  113. assert len(data) == 10
  114. response = self.client.post(url, data, format='json', **self.header)
  115. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  116. self.assertEqual(len(response.data), 10)
  117. class ASNTest(APIViewTestCases.APIViewTestCase):
  118. model = ASN
  119. brief_fields = ['asn', 'description', 'display', 'id', 'url']
  120. bulk_update_data = {
  121. 'description': 'New description',
  122. }
  123. @classmethod
  124. def setUpTestData(cls):
  125. rirs = (
  126. RIR(name='RIR 1', slug='rir-1', is_private=True),
  127. RIR(name='RIR 2', slug='rir-2', is_private=True),
  128. )
  129. RIR.objects.bulk_create(rirs)
  130. sites = (
  131. Site(name='Site 1', slug='site-1'),
  132. Site(name='Site 2', slug='site-2')
  133. )
  134. Site.objects.bulk_create(sites)
  135. tenants = (
  136. Tenant(name='Tenant 1', slug='tenant-1'),
  137. Tenant(name='Tenant 2', slug='tenant-2'),
  138. )
  139. Tenant.objects.bulk_create(tenants)
  140. asns = (
  141. ASN(asn=65000, rir=rirs[0], tenant=tenants[0]),
  142. ASN(asn=65001, rir=rirs[0], tenant=tenants[1]),
  143. ASN(asn=4200000000, rir=rirs[1], tenant=tenants[0]),
  144. ASN(asn=4200000001, rir=rirs[1], tenant=tenants[1]),
  145. )
  146. ASN.objects.bulk_create(asns)
  147. asns[0].sites.set([sites[0]])
  148. asns[1].sites.set([sites[1]])
  149. asns[2].sites.set([sites[0]])
  150. asns[3].sites.set([sites[1]])
  151. cls.create_data = [
  152. {
  153. 'asn': 64512,
  154. 'rir': rirs[0].pk,
  155. },
  156. {
  157. 'asn': 65002,
  158. 'rir': rirs[0].pk,
  159. },
  160. {
  161. 'asn': 4200000002,
  162. 'rir': rirs[1].pk,
  163. },
  164. ]
  165. class VRFTest(APIViewTestCases.APIViewTestCase):
  166. model = VRF
  167. brief_fields = ['description', 'display', 'id', 'name', 'prefix_count', 'rd', 'url']
  168. create_data = [
  169. {
  170. 'name': 'VRF 4',
  171. 'rd': '65000:4',
  172. },
  173. {
  174. 'name': 'VRF 5',
  175. 'rd': '65000:5',
  176. },
  177. {
  178. 'name': 'VRF 6',
  179. 'rd': '65000:6',
  180. },
  181. ]
  182. bulk_update_data = {
  183. 'description': 'New description',
  184. }
  185. @classmethod
  186. def setUpTestData(cls):
  187. vrfs = (
  188. VRF(name='VRF 1', rd='65000:1'),
  189. VRF(name='VRF 2', rd='65000:2'),
  190. VRF(name='VRF 3'), # No RD
  191. )
  192. VRF.objects.bulk_create(vrfs)
  193. class RouteTargetTest(APIViewTestCases.APIViewTestCase):
  194. model = RouteTarget
  195. brief_fields = ['description', 'display', 'id', 'name', 'url']
  196. create_data = [
  197. {
  198. 'name': '65000:1004',
  199. },
  200. {
  201. 'name': '65000:1005',
  202. },
  203. {
  204. 'name': '65000:1006',
  205. },
  206. ]
  207. bulk_update_data = {
  208. 'description': 'New description',
  209. }
  210. @classmethod
  211. def setUpTestData(cls):
  212. route_targets = (
  213. RouteTarget(name='65000:1001'),
  214. RouteTarget(name='65000:1002'),
  215. RouteTarget(name='65000:1003'),
  216. )
  217. RouteTarget.objects.bulk_create(route_targets)
  218. class RIRTest(APIViewTestCases.APIViewTestCase):
  219. model = RIR
  220. brief_fields = ['aggregate_count', 'description', 'display', 'id', 'name', 'slug', 'url']
  221. create_data = [
  222. {
  223. 'name': 'RIR 4',
  224. 'slug': 'rir-4',
  225. },
  226. {
  227. 'name': 'RIR 5',
  228. 'slug': 'rir-5',
  229. },
  230. {
  231. 'name': 'RIR 6',
  232. 'slug': 'rir-6',
  233. },
  234. ]
  235. bulk_update_data = {
  236. 'description': 'New description',
  237. }
  238. @classmethod
  239. def setUpTestData(cls):
  240. rirs = (
  241. RIR(name='RIR 1', slug='rir-1'),
  242. RIR(name='RIR 2', slug='rir-2'),
  243. RIR(name='RIR 3', slug='rir-3'),
  244. )
  245. RIR.objects.bulk_create(rirs)
  246. class AggregateTest(APIViewTestCases.APIViewTestCase):
  247. model = Aggregate
  248. brief_fields = ['description', 'display', 'family', 'id', 'prefix', 'url']
  249. bulk_update_data = {
  250. 'description': 'New description',
  251. }
  252. @classmethod
  253. def setUpTestData(cls):
  254. rirs = (
  255. RIR(name='RIR 1', slug='rir-1'),
  256. RIR(name='RIR 2', slug='rir-2'),
  257. )
  258. RIR.objects.bulk_create(rirs)
  259. aggregates = (
  260. Aggregate(prefix=IPNetwork('10.0.0.0/8'), rir=rirs[0]),
  261. Aggregate(prefix=IPNetwork('172.16.0.0/12'), rir=rirs[0]),
  262. Aggregate(prefix=IPNetwork('192.168.0.0/16'), rir=rirs[0]),
  263. )
  264. Aggregate.objects.bulk_create(aggregates)
  265. cls.create_data = [
  266. {
  267. 'prefix': '100.0.0.0/8',
  268. 'rir': rirs[1].pk,
  269. },
  270. {
  271. 'prefix': '101.0.0.0/8',
  272. 'rir': rirs[1].pk,
  273. },
  274. {
  275. 'prefix': '102.0.0.0/8',
  276. 'rir': rirs[1].pk,
  277. },
  278. ]
  279. @tag('regression')
  280. def test_graphql_aggregate_prefix_exact(self):
  281. """
  282. Test case to verify aggregate prefix equality via field lookup in GraphQL API.
  283. """
  284. self.add_permissions('ipam.view_aggregate', 'ipam.view_rir')
  285. rir = RIR.objects.create(name='RFC6598', slug='rfc6598', is_private=True)
  286. aggregate1 = Aggregate.objects.create(prefix='100.64.0.0/10', rir=rir)
  287. Aggregate.objects.create(prefix='203.0.113.0/24', rir=rir)
  288. url = reverse('graphql')
  289. query = """{
  290. aggregate_list(filters: { prefix: { exact: "100.64.0.0/10" } }) { prefix }
  291. }"""
  292. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  293. self.assertHttpStatus(response, status.HTTP_200_OK)
  294. data = response.json()
  295. self.assertNotIn('errors', data)
  296. prefixes = {row['prefix'] for row in data['data']['aggregate_list']}
  297. self.assertIn(str(aggregate1.prefix), prefixes)
  298. @tag('regression')
  299. def test_graphql_aggregate_contains_skips_invalid(self):
  300. """
  301. Test the GraphQL API Aggregate `contains` filter skips invalid input.
  302. """
  303. self.add_permissions('ipam.view_aggregate', 'ipam.view_rir')
  304. rir = RIR.objects.create(name='RIR 3', slug='rir-3', is_private=False)
  305. aggregate1 = Aggregate.objects.create(prefix='100.64.0.0/10', rir=rir)
  306. Aggregate.objects.create(prefix='203.0.113.0/24', rir=rir)
  307. url = reverse('graphql')
  308. query = """{
  309. aggregate_list(filters: { contains: ["100.64.16.0/24", "not-a-cidr", ""] }) { prefix }
  310. }"""
  311. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  312. self.assertHttpStatus(response, status.HTTP_200_OK)
  313. data = response.json()
  314. self.assertNotIn('errors', data)
  315. prefixes = {row['prefix'] for row in data['data']['aggregate_list']}
  316. self.assertIn(str(aggregate1.prefix), prefixes)
  317. # No exception occurred; invalid entries were ignored
  318. class RoleTest(APIViewTestCases.APIViewTestCase):
  319. model = Role
  320. brief_fields = ['description', 'display', 'id', 'name', 'prefix_count', 'slug', 'url', 'vlan_count']
  321. create_data = [
  322. {
  323. 'name': 'Role 4',
  324. 'slug': 'role-4',
  325. },
  326. {
  327. 'name': 'Role 5',
  328. 'slug': 'role-5',
  329. },
  330. {
  331. 'name': 'Role 6',
  332. 'slug': 'role-6',
  333. },
  334. ]
  335. bulk_update_data = {
  336. 'description': 'New description',
  337. }
  338. @classmethod
  339. def setUpTestData(cls):
  340. roles = (
  341. Role(name='Role 1', slug='role-1'),
  342. Role(name='Role 2', slug='role-2'),
  343. Role(name='Role 3', slug='role-3'),
  344. )
  345. Role.objects.bulk_create(roles)
  346. class PrefixTest(APIViewTestCases.APIViewTestCase):
  347. model = Prefix
  348. brief_fields = ['_depth', 'description', 'display', 'family', 'id', 'prefix', 'url']
  349. create_data = [
  350. {
  351. 'prefix': '192.168.4.0/24',
  352. },
  353. {
  354. 'prefix': '192.168.5.0/24',
  355. },
  356. {
  357. 'prefix': '192.168.6.0/24',
  358. },
  359. ]
  360. bulk_update_data = {
  361. 'description': 'New description',
  362. }
  363. @classmethod
  364. def setUpTestData(cls):
  365. prefixes = (
  366. Prefix(prefix=IPNetwork('192.168.1.0/24')),
  367. Prefix(prefix=IPNetwork('192.168.2.0/24')),
  368. Prefix(prefix=IPNetwork('192.168.3.0/24')),
  369. )
  370. Prefix.objects.bulk_create(prefixes)
  371. @tag('regression')
  372. def test_clean_validates_scope(self):
  373. prefix = Prefix.objects.first()
  374. site = Site.objects.create(name='Test Site', slug='test-site')
  375. data = {'scope_type': 'dcim.site', 'scope_id': site.id}
  376. url = reverse('ipam-api:prefix-detail', kwargs={'pk': prefix.pk})
  377. self.add_permissions('ipam.change_prefix')
  378. response = self.client.patch(url, data, format='json', **self.header)
  379. self.assertHttpStatus(response, status.HTTP_200_OK)
  380. def test_list_available_prefixes(self):
  381. """
  382. Test retrieval of all available prefixes within a parent prefix.
  383. """
  384. vrf = VRF.objects.create(name='VRF 1')
  385. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), vrf=vrf)
  386. Prefix.objects.create(prefix=IPNetwork('192.0.2.64/26'), vrf=vrf)
  387. Prefix.objects.create(prefix=IPNetwork('192.0.2.192/27'), vrf=vrf)
  388. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  389. self.add_permissions('ipam.view_prefix')
  390. # Retrieve all available IPs
  391. response = self.client.get(url, **self.header)
  392. available_prefixes = ['192.0.2.0/26', '192.0.2.128/26', '192.0.2.224/27']
  393. for i, p in enumerate(response.data):
  394. self.assertEqual(p['prefix'], available_prefixes[i])
  395. def test_create_single_available_prefix(self):
  396. """
  397. Test retrieval of the first available prefix within a parent prefix.
  398. """
  399. vrf = VRF.objects.create(name='VRF 1')
  400. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), vrf=vrf, is_pool=True)
  401. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  402. self.add_permissions('ipam.view_prefix', 'ipam.add_prefix')
  403. # Create four available prefixes with individual requests
  404. prefixes_to_be_created = [
  405. '192.0.2.0/30',
  406. '192.0.2.4/30',
  407. '192.0.2.8/30',
  408. '192.0.2.12/30',
  409. ]
  410. for i in range(4):
  411. data = {
  412. 'prefix_length': 30,
  413. 'description': 'Test Prefix {}'.format(i + 1)
  414. }
  415. response = self.client.post(url, data, format='json', **self.header)
  416. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  417. self.assertEqual(response.data['prefix'], prefixes_to_be_created[i])
  418. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  419. self.assertEqual(response.data['description'], data['description'])
  420. # Try to create one more prefix
  421. response = self.client.post(url, {'prefix_length': 30}, format='json', **self.header)
  422. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  423. self.assertIn('detail', response.data)
  424. # Try to create invalid prefix type
  425. response = self.client.post(url, {'prefix_length': '30'}, format='json', **self.header)
  426. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  427. self.assertIn('prefix_length', response.data[0])
  428. def test_create_multiple_available_prefixes(self):
  429. """
  430. Test the creation of available prefixes within a parent prefix.
  431. """
  432. vrf = VRF.objects.create(name='VRF 1')
  433. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), vrf=vrf, is_pool=True)
  434. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  435. self.add_permissions('ipam.view_prefix', 'ipam.add_prefix')
  436. # Try to create five /30s (only four are available)
  437. data = [
  438. {'prefix_length': 30, 'description': 'Prefix 1'},
  439. {'prefix_length': 30, 'description': 'Prefix 2'},
  440. {'prefix_length': 30, 'description': 'Prefix 3'},
  441. {'prefix_length': 30, 'description': 'Prefix 4'},
  442. {'prefix_length': 30, 'description': 'Prefix 5'},
  443. ]
  444. response = self.client.post(url, data, format='json', **self.header)
  445. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  446. self.assertIn('detail', response.data)
  447. # Verify that no prefixes were created (the entire /28 is still available)
  448. response = self.client.get(url, **self.header)
  449. self.assertHttpStatus(response, status.HTTP_200_OK)
  450. self.assertEqual(response.data[0]['prefix'], '192.0.2.0/28')
  451. # Create four /30s in a single request
  452. response = self.client.post(url, data[:4], format='json', **self.header)
  453. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  454. self.assertEqual(len(response.data), 4)
  455. def test_list_available_ips(self):
  456. """
  457. Test retrieval of all available IP addresses within a parent prefix.
  458. """
  459. vrf = VRF.objects.create(name='VRF 1')
  460. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), vrf=vrf, is_pool=True)
  461. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  462. self.add_permissions('ipam.view_prefix', 'ipam.view_ipaddress')
  463. # Retrieve all available IPs
  464. response = self.client.get(url, **self.header)
  465. self.assertHttpStatus(response, status.HTTP_200_OK)
  466. self.assertEqual(len(response.data), 8) # 8 because prefix.is_pool = True
  467. # Change the prefix to not be a pool and try again
  468. prefix.is_pool = False
  469. prefix.save()
  470. response = self.client.get(url, **self.header)
  471. self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.is_pool = False
  472. def test_create_single_available_ip(self):
  473. """
  474. Test retrieval of the first available IP address within a parent prefix.
  475. """
  476. vrf = VRF.objects.create(name='VRF 1')
  477. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), vrf=vrf, is_pool=True)
  478. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  479. self.add_permissions('ipam.view_prefix', 'ipam.add_ipaddress')
  480. # Create all four available IPs with individual requests
  481. for i in range(1, 5):
  482. data = {
  483. 'description': 'Test IP {}'.format(i)
  484. }
  485. response = self.client.post(url, data, format='json', **self.header)
  486. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  487. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  488. self.assertEqual(response.data['description'], data['description'])
  489. # Try to create one more IP
  490. response = self.client.post(url, {}, format='json', **self.header)
  491. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  492. self.assertIn('detail', response.data)
  493. def test_create_multiple_available_ips(self):
  494. """
  495. Test the creation of available IP addresses within a parent prefix.
  496. """
  497. vrf = VRF.objects.create(name='VRF 1')
  498. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), vrf=vrf, is_pool=True)
  499. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  500. self.add_permissions('ipam.view_prefix', 'ipam.add_ipaddress')
  501. # Try to create nine IPs (only eight are available)
  502. data = [{'description': f'Test IP {i}'} for i in range(1, 10)] # 9 IPs
  503. response = self.client.post(url, data, format='json', **self.header)
  504. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  505. self.assertIn('detail', response.data)
  506. # Create all eight available IPs in a single request
  507. data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 9)] # 8 IPs
  508. response = self.client.post(url, data, format='json', **self.header)
  509. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  510. self.assertEqual(len(response.data), 8)
  511. @tag('regression')
  512. def test_graphql_tenant_prefixes_contains_nested_skips_invalid(self):
  513. """
  514. Test the GraphQL API Tenant nested Prefix `contains` filter skips invalid input.
  515. """
  516. self.add_permissions('ipam.view_prefix', 'ipam.view_vrf', 'tenancy.view_tenant')
  517. tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
  518. vrf = VRF.objects.create(name='Test VRF 1', rd='64512:1')
  519. Prefix.objects.create(prefix='10.20.0.0/16', vrf=vrf, tenant=tenant)
  520. Prefix.objects.create(prefix='198.51.100.0/24', vrf=vrf) # non-tenant
  521. url = reverse('graphql')
  522. query = """{
  523. tenant_list(filters: { prefixes: { contains: ["10.20.1.0/24", "not-a-cidr"] } }) { id }
  524. }"""
  525. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  526. self.assertHttpStatus(response, status.HTTP_200_OK)
  527. data = response.json()
  528. self.assertNotIn('errors', data)
  529. self.assertTrue(data['data']['tenant_list']) # tenant returned
  530. class IPRangeTest(APIViewTestCases.APIViewTestCase):
  531. model = IPRange
  532. brief_fields = ['description', 'display', 'end_address', 'family', 'id', 'start_address', 'url']
  533. create_data = [
  534. {
  535. 'start_address': '192.168.4.10/24',
  536. 'end_address': '192.168.4.50/24',
  537. },
  538. {
  539. 'start_address': '192.168.5.10/24',
  540. 'end_address': '192.168.5.50/24',
  541. },
  542. {
  543. 'start_address': '192.168.6.10/24',
  544. 'end_address': '192.168.6.50/24',
  545. },
  546. ]
  547. bulk_update_data = {
  548. 'description': 'New description',
  549. }
  550. @classmethod
  551. def setUpTestData(cls):
  552. ip_ranges = (
  553. IPRange(start_address=IPNetwork('192.168.1.10/24'), end_address=IPNetwork('192.168.1.50/24'), size=51),
  554. IPRange(start_address=IPNetwork('192.168.2.10/24'), end_address=IPNetwork('192.168.2.50/24'), size=51),
  555. IPRange(start_address=IPNetwork('192.168.3.10/24'), end_address=IPNetwork('192.168.3.50/24'), size=51),
  556. )
  557. IPRange.objects.bulk_create(ip_ranges)
  558. def test_list_available_ips(self):
  559. """
  560. Test retrieval of all available IP addresses within a parent IP range.
  561. """
  562. iprange = IPRange.objects.create(
  563. start_address=IPNetwork('192.0.2.10/24'),
  564. end_address=IPNetwork('192.0.2.19/24')
  565. )
  566. url = reverse('ipam-api:iprange-available-ips', kwargs={'pk': iprange.pk})
  567. self.add_permissions('ipam.view_iprange', 'ipam.view_ipaddress')
  568. # Retrieve all available IPs
  569. response = self.client.get(url, **self.header)
  570. self.assertHttpStatus(response, status.HTTP_200_OK)
  571. self.assertEqual(len(response.data), 10)
  572. def test_create_single_available_ip(self):
  573. """
  574. Test retrieval of the first available IP address within a parent IP range.
  575. """
  576. vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
  577. iprange = IPRange.objects.create(
  578. start_address=IPNetwork('192.0.2.1/24'),
  579. end_address=IPNetwork('192.0.2.3/24'),
  580. vrf=vrf
  581. )
  582. url = reverse('ipam-api:iprange-available-ips', kwargs={'pk': iprange.pk})
  583. self.add_permissions('ipam.view_iprange', 'ipam.add_ipaddress')
  584. # Create all three available IPs with individual requests
  585. for i in range(1, 4):
  586. data = {
  587. 'description': f'Test IP #{i}'
  588. }
  589. response = self.client.post(url, data, format='json', **self.header)
  590. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  591. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  592. self.assertEqual(response.data['description'], data['description'])
  593. # Try to create one more IP
  594. response = self.client.post(url, {}, format='json', **self.header)
  595. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  596. self.assertIn('detail', response.data)
  597. def test_create_multiple_available_ips(self):
  598. """
  599. Test the creation of available IP addresses within a parent IP range.
  600. """
  601. iprange = IPRange.objects.create(
  602. start_address=IPNetwork('192.0.2.1/24'),
  603. end_address=IPNetwork('192.0.2.8/24')
  604. )
  605. url = reverse('ipam-api:iprange-available-ips', kwargs={'pk': iprange.pk})
  606. self.add_permissions('ipam.view_iprange', 'ipam.add_ipaddress')
  607. # Try to create nine IPs (only eight are available)
  608. data = [{'description': f'Test IP #{i}'} for i in range(1, 10)] # 9 IPs
  609. response = self.client.post(url, data, format='json', **self.header)
  610. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  611. self.assertIn('detail', response.data)
  612. # Create all eight available IPs in a single request
  613. data = [{'description': f'Test IP #{i}'} for i in range(1, 9)] # 8 IPs
  614. response = self.client.post(url, data, format='json', **self.header)
  615. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  616. self.assertEqual(len(response.data), 8)
  617. @tag('regression')
  618. def test_graphql_tenant_ip_ranges_parent_nested_skips_invalid(self):
  619. """
  620. Test the GraphQL API Tenant nested IP Range `parent` filter skips invalid input.
  621. """
  622. self.add_permissions('tenancy.view_tenant', 'ipam.view_iprange', 'ipam.view_vrf')
  623. tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
  624. vrf = VRF.objects.create(name='Test VRF 1', rd='64512:1')
  625. IPRange.objects.create(
  626. start_address=IPNetwork('10.30.0.1/24'), end_address=IPNetwork('10.30.0.255/24'), vrf=vrf, tenant=tenant
  627. )
  628. IPRange.objects.create(
  629. start_address=IPNetwork('10.31.0.1/24'), end_address=IPNetwork('10.31.0.255/24'), vrf=vrf, tenant=tenant
  630. )
  631. url = reverse('graphql')
  632. query = """{
  633. tenant_list(filters: {
  634. name: { exact: "Tenant 1" }
  635. ip_ranges: { parent: ["10.30.0.0/24", "bogus"] }
  636. }) { id }
  637. }"""
  638. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  639. self.assertHttpStatus(response, status.HTTP_200_OK)
  640. data = response.json()
  641. self.assertNotIn('errors', data)
  642. self.assertTrue(data['data']['tenant_list']) # tenant returned
  643. # No exception occurred; invalid entries were ignored
  644. @tag('regression')
  645. def test_graphql_tenant_ip_ranges_contains_nested_skips_invalid(self):
  646. """
  647. Test the GraphQL API Tenant nested IP Range `contains` filter skips invalid input.
  648. """
  649. self.add_permissions('tenancy.view_tenant', 'ipam.view_iprange', 'ipam.view_vrf')
  650. tenant = Tenant.objects.create(name='Tenant 2', slug='tenant-2')
  651. vrf = VRF.objects.create(name='Test VRF 1', rd='64512:2')
  652. IPRange.objects.create(
  653. start_address=IPNetwork('10.40.0.1/24'), end_address=IPNetwork('10.40.0.255/24'), vrf=vrf, tenant=tenant
  654. )
  655. url = reverse('graphql')
  656. query = """{
  657. tenant_list(filters: {
  658. name: { exact: "Tenant 2" }
  659. ip_ranges: { contains: ["10.40.0.128/25", "###"] }
  660. }) { id }
  661. }"""
  662. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  663. self.assertHttpStatus(response, status.HTTP_200_OK)
  664. data = response.json()
  665. self.assertNotIn('errors', data)
  666. self.assertTrue(data['data']['tenant_list']) # tenant returned
  667. # No exception occurred; invalid entries were ignored
  668. class IPAddressTest(APIViewTestCases.APIViewTestCase):
  669. model = IPAddress
  670. brief_fields = ['address', 'description', 'display', 'family', 'id', 'url']
  671. create_data = [
  672. {
  673. 'address': '192.168.0.4/24',
  674. },
  675. {
  676. 'address': '192.168.0.5/24',
  677. },
  678. {
  679. 'address': '192.168.0.6/24',
  680. },
  681. ]
  682. bulk_update_data = {
  683. 'description': 'New description',
  684. }
  685. graphql_filter = {
  686. 'address': {'lookup': 'i_exact', 'value': '192.168.0.1/24'},
  687. }
  688. @classmethod
  689. def setUpTestData(cls):
  690. ip_addresses = (
  691. IPAddress(address=IPNetwork('192.168.0.1/24')),
  692. IPAddress(address=IPNetwork('192.168.0.2/24')),
  693. IPAddress(address=IPNetwork('192.168.0.3/24')),
  694. )
  695. IPAddress.objects.bulk_create(ip_addresses)
  696. def test_assign_object(self):
  697. """
  698. Test the creation of available IP addresses within a parent IP range.
  699. """
  700. site = Site.objects.create(name='Site 1')
  701. manufacturer = Manufacturer.objects.create(name='Manufacturer 1')
  702. device_type = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
  703. role = DeviceRole.objects.create(name='Switch')
  704. device1 = Device.objects.create(
  705. name='Device 1',
  706. site=site,
  707. device_type=device_type,
  708. role=role,
  709. status='active'
  710. )
  711. interface1 = Interface.objects.create(name='Interface 1', device=device1, type='1000baset')
  712. interface2 = Interface.objects.create(name='Interface 2', device=device1, type='1000baset')
  713. device2 = Device.objects.create(
  714. name='Device 2',
  715. site=site,
  716. device_type=device_type,
  717. role=role,
  718. status='active'
  719. )
  720. interface3 = Interface.objects.create(name='Interface 3', device=device2, type='1000baset')
  721. ip_addresses = (
  722. IPAddress(address=IPNetwork('192.168.0.4/24'), assigned_object=interface1),
  723. IPAddress(address=IPNetwork('192.168.1.4/24')),
  724. )
  725. IPAddress.objects.bulk_create(ip_addresses)
  726. ip1 = ip_addresses[0]
  727. ip1.assigned_object = interface1
  728. device1.primary_ip4 = ip_addresses[0]
  729. device1.save()
  730. url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': ip1.pk})
  731. self.add_permissions('ipam.change_ipaddress')
  732. # assign to same parent
  733. data = {
  734. 'assigned_object_id': interface2.pk
  735. }
  736. response = self.client.patch(url, data, format='json', **self.header)
  737. self.assertHttpStatus(response, status.HTTP_200_OK)
  738. # assign to same different parent - should error
  739. data = {
  740. 'assigned_object_id': interface3.pk
  741. }
  742. response = self.client.patch(url, data, format='json', **self.header)
  743. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  744. @tag('regression')
  745. def test_graphql_device_primary_ip4_assigned_nested(self):
  746. """
  747. Test the GraphQL API Device nested IP Address `primary_ip4` filter.
  748. """
  749. self.add_permissions('dcim.view_device', 'dcim.view_interface', 'ipam.view_ipaddress')
  750. site = Site.objects.create(name='Site 1')
  751. manufacturer = Manufacturer.objects.create(name='Manufacturer 1')
  752. device_type = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
  753. role = DeviceRole.objects.create(name='Switch')
  754. device1 = Device.objects.create(name='Device 1', site=site, device_type=device_type, role=role, status='active')
  755. interface1 = Interface.objects.create(name='Interface 1', device=device1, type='1000baset')
  756. ip1 = IPAddress.objects.create(address='10.0.0.1/24')
  757. ip1.assigned_object = interface1
  758. ip1.save()
  759. device1.primary_ip4 = ip1
  760. device1.save()
  761. device2 = Device.objects.create(name='Device 2', site=site, device_type=device_type, role=role, status='active')
  762. url = reverse('graphql')
  763. query = """{
  764. device_list(filters: { primary_ip4: { assigned: true } }) { id name }
  765. }"""
  766. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  767. self.assertHttpStatus(response, status.HTTP_200_OK)
  768. data = response.json()
  769. self.assertNotIn('errors', data)
  770. ids = {row['id'] for row in data['data']['device_list']}
  771. self.assertIn(str(device1.pk), ids)
  772. self.assertNotIn(str(device2.pk), ids)
  773. @tag('regression')
  774. def test_graphql_device_primary_ip4_parent_nested_skips_invalid(self):
  775. """
  776. Test the GraphQL API Device nested IP Address `parent` filter skips invalid input.
  777. """
  778. self.add_permissions('dcim.view_device', 'dcim.view_interface', 'ipam.view_ipaddress')
  779. site = Site.objects.create(name='Site 1')
  780. manufacturer = Manufacturer.objects.create(name='Manufacturer 1')
  781. device_type = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
  782. role = DeviceRole.objects.create(name='Switch')
  783. device1 = Device.objects.create(name='Device 1', site=site, device_type=device_type, role=role, status='active')
  784. interface1 = Interface.objects.create(name='Interface 1', device=device1, type='1000baset')
  785. ip1 = IPAddress.objects.create(address='192.0.2.10/24')
  786. ip1.assigned_object = interface1
  787. ip1.save()
  788. device1.primary_ip4 = ip1
  789. device1.save()
  790. url = reverse('graphql')
  791. query = """{
  792. device_list(filters: { primary_ip4: { parent: ["192.0.2.0/24", "bad-cidr"] } }) { id }
  793. }"""
  794. response = self.client.post(url, data={'query': query}, format='json', **self.header)
  795. self.assertHttpStatus(response, status.HTTP_200_OK)
  796. data = response.json()
  797. self.assertNotIn('errors', data)
  798. ids = {row['id'] for row in data['data']['device_list']}
  799. self.assertIn(str(device1.pk), ids)
  800. class FHRPGroupTest(APIViewTestCases.APIViewTestCase):
  801. model = FHRPGroup
  802. brief_fields = ['description', 'display', 'group_id', 'id', 'protocol', 'url']
  803. bulk_update_data = {
  804. 'protocol': FHRPGroupProtocolChoices.PROTOCOL_GLBP,
  805. 'group_id': 200,
  806. 'auth_type': FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
  807. 'auth_key': 'foobarbaz999',
  808. 'name': 'foobar-999',
  809. 'description': 'New description',
  810. }
  811. @classmethod
  812. def setUpTestData(cls):
  813. fhrp_groups = (
  814. FHRPGroup(
  815. protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2,
  816. group_id=10,
  817. auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT,
  818. auth_key='foobar123',
  819. ),
  820. FHRPGroup(
  821. protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP3,
  822. group_id=20,
  823. auth_type=FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
  824. auth_key='foobar123',
  825. ),
  826. FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_HSRP, group_id=30),
  827. )
  828. FHRPGroup.objects.bulk_create(fhrp_groups)
  829. cls.create_data = [
  830. {
  831. 'protocol': FHRPGroupProtocolChoices.PROTOCOL_VRRP2,
  832. 'group_id': 110,
  833. 'auth_type': FHRPGroupAuthTypeChoices.AUTHENTICATION_PLAINTEXT,
  834. 'auth_key': 'foobar123',
  835. },
  836. {
  837. 'protocol': FHRPGroupProtocolChoices.PROTOCOL_VRRP3,
  838. 'group_id': 120,
  839. 'auth_type': FHRPGroupAuthTypeChoices.AUTHENTICATION_MD5,
  840. 'auth_key': 'barfoo456',
  841. },
  842. {
  843. 'protocol': FHRPGroupProtocolChoices.PROTOCOL_GLBP,
  844. 'group_id': 130,
  845. },
  846. ]
  847. class FHRPGroupAssignmentTest(APIViewTestCases.APIViewTestCase):
  848. model = FHRPGroupAssignment
  849. brief_fields = ['display', 'group', 'id', 'interface_id', 'interface_type', 'priority', 'url']
  850. bulk_update_data = {
  851. 'priority': 100,
  852. }
  853. user_permissions = ('ipam.view_fhrpgroup', )
  854. @classmethod
  855. def setUpTestData(cls):
  856. device1 = create_test_device('device1')
  857. device2 = create_test_device('device2')
  858. device3 = create_test_device('device3')
  859. interfaces = (
  860. Interface(device=device1, name='eth0', type='other'),
  861. Interface(device=device1, name='eth1', type='other'),
  862. Interface(device=device1, name='eth2', type='other'),
  863. Interface(device=device2, name='eth0', type='other'),
  864. Interface(device=device2, name='eth1', type='other'),
  865. Interface(device=device2, name='eth2', type='other'),
  866. Interface(device=device3, name='eth0', type='other'),
  867. Interface(device=device3, name='eth1', type='other'),
  868. Interface(device=device3, name='eth2', type='other'),
  869. )
  870. Interface.objects.bulk_create(interfaces)
  871. ip_addresses = (
  872. IPAddress(address=IPNetwork('192.168.0.2/24'), assigned_object=interfaces[0]),
  873. IPAddress(address=IPNetwork('192.168.1.2/24'), assigned_object=interfaces[1]),
  874. IPAddress(address=IPNetwork('192.168.2.2/24'), assigned_object=interfaces[2]),
  875. IPAddress(address=IPNetwork('192.168.0.3/24'), assigned_object=interfaces[3]),
  876. IPAddress(address=IPNetwork('192.168.1.3/24'), assigned_object=interfaces[4]),
  877. IPAddress(address=IPNetwork('192.168.2.3/24'), assigned_object=interfaces[5]),
  878. IPAddress(address=IPNetwork('192.168.0.4/24'), assigned_object=interfaces[6]),
  879. IPAddress(address=IPNetwork('192.168.1.4/24'), assigned_object=interfaces[7]),
  880. IPAddress(address=IPNetwork('192.168.2.4/24'), assigned_object=interfaces[8]),
  881. )
  882. IPAddress.objects.bulk_create(ip_addresses)
  883. fhrp_groups = (
  884. FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2, group_id=10),
  885. FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2, group_id=20),
  886. FHRPGroup(protocol=FHRPGroupProtocolChoices.PROTOCOL_VRRP2, group_id=30),
  887. )
  888. FHRPGroup.objects.bulk_create(fhrp_groups)
  889. fhrp_group_assignments = (
  890. FHRPGroupAssignment(group=fhrp_groups[0], interface=interfaces[0], priority=10),
  891. FHRPGroupAssignment(group=fhrp_groups[1], interface=interfaces[1], priority=10),
  892. FHRPGroupAssignment(group=fhrp_groups[2], interface=interfaces[2], priority=10),
  893. FHRPGroupAssignment(group=fhrp_groups[0], interface=interfaces[3], priority=20),
  894. FHRPGroupAssignment(group=fhrp_groups[1], interface=interfaces[4], priority=20),
  895. FHRPGroupAssignment(group=fhrp_groups[2], interface=interfaces[5], priority=20),
  896. )
  897. FHRPGroupAssignment.objects.bulk_create(fhrp_group_assignments)
  898. cls.create_data = [
  899. {
  900. 'group': fhrp_groups[0].pk,
  901. 'interface_type': 'dcim.interface',
  902. 'interface_id': interfaces[6].pk,
  903. 'priority': 30,
  904. },
  905. {
  906. 'group': fhrp_groups[1].pk,
  907. 'interface_type': 'dcim.interface',
  908. 'interface_id': interfaces[7].pk,
  909. 'priority': 30,
  910. },
  911. {
  912. 'group': fhrp_groups[2].pk,
  913. 'interface_type': 'dcim.interface',
  914. 'interface_id': interfaces[8].pk,
  915. 'priority': 30,
  916. },
  917. ]
  918. class VLANGroupTest(APIViewTestCases.APIViewTestCase):
  919. model = VLANGroup
  920. brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url', 'vlan_count']
  921. create_data = [
  922. {
  923. 'name': 'VLAN Group 4',
  924. 'slug': 'vlan-group-4',
  925. },
  926. {
  927. 'name': 'VLAN Group 5',
  928. 'slug': 'vlan-group-5',
  929. },
  930. {
  931. 'name': 'VLAN Group 6',
  932. 'slug': 'vlan-group-6',
  933. },
  934. ]
  935. bulk_update_data = {
  936. 'description': 'New description',
  937. }
  938. @classmethod
  939. def setUpTestData(cls):
  940. vlan_groups = (
  941. VLANGroup(name='VLAN Group 1', slug='vlan-group-1'),
  942. VLANGroup(name='VLAN Group 2', slug='vlan-group-2'),
  943. VLANGroup(name='VLAN Group 3', slug='vlan-group-3'),
  944. )
  945. VLANGroup.objects.bulk_create(vlan_groups)
  946. def test_list_available_vlans(self):
  947. """
  948. Test retrieval of all available VLANs within a group.
  949. """
  950. MIN_VID = 100
  951. MAX_VID = 199
  952. self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan')
  953. vlangroup = VLANGroup.objects.create(
  954. name='VLAN Group X',
  955. slug='vlan-group-x',
  956. vid_ranges=string_to_ranges(f"{MIN_VID}-{MAX_VID}")
  957. )
  958. # Create a set of VLANs within the group
  959. vlans = (
  960. VLAN(vid=10, name='VLAN 10', group=vlangroup),
  961. VLAN(vid=20, name='VLAN 20', group=vlangroup),
  962. VLAN(vid=30, name='VLAN 30', group=vlangroup),
  963. )
  964. VLAN.objects.bulk_create(vlans)
  965. # Retrieve all available VLANs
  966. url = reverse('ipam-api:vlangroup-available-vlans', kwargs={'pk': vlangroup.pk})
  967. response = self.client.get(f'{url}?limit=0', **self.header)
  968. self.assertEqual(len(response.data), MAX_VID - MIN_VID + 1)
  969. available_vlans = {vlan['vid'] for vlan in response.data}
  970. for vlan in vlans:
  971. self.assertNotIn(vlan.vid, available_vlans)
  972. # Retrieve a maximum number of available VLANs
  973. url = reverse('ipam-api:vlangroup-available-vlans', kwargs={'pk': vlangroup.pk})
  974. response = self.client.get(f'{url}?limit=10', **self.header)
  975. self.assertEqual(len(response.data), 10)
  976. def test_create_single_available_vlan(self):
  977. """
  978. Test the creation of a single available VLAN.
  979. """
  980. self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan', 'ipam.add_vlan')
  981. vlangroup = VLANGroup.objects.first()
  982. VLAN.objects.create(vid=1, name='VLAN 1', group=vlangroup)
  983. data = {
  984. "name": "First VLAN",
  985. }
  986. url = reverse('ipam-api:vlangroup-available-vlans', kwargs={'pk': vlangroup.pk})
  987. response = self.client.post(url, data, format='json', **self.header)
  988. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  989. self.assertEqual(response.data['name'], data['name'])
  990. self.assertEqual(response.data['group']['id'], vlangroup.pk)
  991. self.assertEqual(response.data['vid'], 2)
  992. def test_create_multiple_available_vlans(self):
  993. """
  994. Test the creation of multiple available VLANs.
  995. """
  996. self.add_permissions('ipam.view_vlangroup', 'ipam.view_vlan', 'ipam.add_vlan')
  997. vlangroup = VLANGroup.objects.first()
  998. vlans = (
  999. VLAN(vid=1, name='VLAN 1', group=vlangroup),
  1000. VLAN(vid=3, name='VLAN 3', group=vlangroup),
  1001. VLAN(vid=5, name='VLAN 5', group=vlangroup),
  1002. )
  1003. VLAN.objects.bulk_create(vlans)
  1004. data = (
  1005. {"name": "First VLAN"},
  1006. {"name": "Second VLAN"},
  1007. {"name": "Third VLAN"},
  1008. )
  1009. url = reverse('ipam-api:vlangroup-available-vlans', kwargs={'pk': vlangroup.pk})
  1010. response = self.client.post(url, data, format='json', **self.header)
  1011. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  1012. self.assertEqual(len(response.data), 3)
  1013. self.assertEqual(response.data[0]['name'], data[0]['name'])
  1014. self.assertEqual(response.data[0]['group']['id'], vlangroup.pk)
  1015. self.assertEqual(response.data[0]['vid'], 2)
  1016. self.assertEqual(response.data[1]['name'], data[1]['name'])
  1017. self.assertEqual(response.data[1]['group']['id'], vlangroup.pk)
  1018. self.assertEqual(response.data[1]['vid'], 4)
  1019. self.assertEqual(response.data[2]['name'], data[2]['name'])
  1020. self.assertEqual(response.data[2]['group']['id'], vlangroup.pk)
  1021. self.assertEqual(response.data[2]['vid'], 6)
  1022. class VLANTest(APIViewTestCases.APIViewTestCase):
  1023. model = VLAN
  1024. brief_fields = ['description', 'display', 'id', 'name', 'url', 'vid']
  1025. bulk_update_data = {
  1026. 'description': 'New description',
  1027. }
  1028. @classmethod
  1029. def setUpTestData(cls):
  1030. vlan_groups = (
  1031. VLANGroup(name='VLAN Group 1', slug='vlan-group-1'),
  1032. VLANGroup(name='VLAN Group 2', slug='vlan-group-2'),
  1033. )
  1034. VLANGroup.objects.bulk_create(vlan_groups)
  1035. vlans = (
  1036. VLAN(name='VLAN 1', vid=1, group=vlan_groups[0]),
  1037. VLAN(name='VLAN 2', vid=2, group=vlan_groups[0]),
  1038. VLAN(name='VLAN 3', vid=3, group=vlan_groups[0]),
  1039. VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
  1040. )
  1041. VLAN.objects.bulk_create(vlans)
  1042. cls.create_data = [
  1043. {
  1044. 'vid': 4,
  1045. 'name': 'VLAN 4',
  1046. 'group': vlan_groups[1].pk,
  1047. },
  1048. {
  1049. 'vid': 5,
  1050. 'name': 'VLAN 5',
  1051. 'group': vlan_groups[1].pk,
  1052. },
  1053. {
  1054. 'vid': 6,
  1055. 'name': 'VLAN 6',
  1056. 'group': vlan_groups[1].pk,
  1057. },
  1058. {
  1059. 'vid': 2001,
  1060. 'name': 'CVLAN 1',
  1061. 'qinq_role': VLANQinQRoleChoices.ROLE_CUSTOMER,
  1062. 'qinq_svlan': vlans[3].pk,
  1063. },
  1064. ]
  1065. def test_delete_vlan_with_prefix(self):
  1066. """
  1067. Attempt and fail to delete a VLAN with a Prefix assigned to it.
  1068. """
  1069. vlan = VLAN.objects.first()
  1070. Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), vlan=vlan)
  1071. self.add_permissions('ipam.delete_vlan')
  1072. url = reverse('ipam-api:vlan-detail', kwargs={'pk': vlan.pk})
  1073. with disable_logging(level=logging.WARNING):
  1074. response = self.client.delete(url, **self.header)
  1075. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  1076. content = json.loads(response.content.decode('utf-8'))
  1077. self.assertIn('detail', content)
  1078. self.assertTrue(content['detail'].startswith('Unable to delete object.'))
  1079. class VLANTranslationPolicyTest(APIViewTestCases.APIViewTestCase):
  1080. model = VLANTranslationPolicy
  1081. brief_fields = ['description', 'display', 'id', 'name', 'url',]
  1082. bulk_update_data = {
  1083. 'description': 'New description',
  1084. }
  1085. @classmethod
  1086. def setUpTestData(cls):
  1087. vlan_translation_policies = (
  1088. VLANTranslationPolicy(
  1089. name='Policy 1',
  1090. description='foobar1',
  1091. ),
  1092. VLANTranslationPolicy(
  1093. name='Policy 2',
  1094. description='foobar2',
  1095. ),
  1096. VLANTranslationPolicy(
  1097. name='Policy 3',
  1098. description='foobar3',
  1099. ),
  1100. )
  1101. VLANTranslationPolicy.objects.bulk_create(vlan_translation_policies)
  1102. cls.create_data = [
  1103. {
  1104. 'name': 'Policy 4',
  1105. 'description': 'foobar4',
  1106. },
  1107. {
  1108. 'name': 'Policy 5',
  1109. 'description': 'foobar5',
  1110. },
  1111. {
  1112. 'name': 'Policy 6',
  1113. 'description': 'foobar6',
  1114. },
  1115. ]
  1116. class VLANTranslationRuleTest(APIViewTestCases.APIViewTestCase):
  1117. model = VLANTranslationRule
  1118. brief_fields = ['description', 'display', 'id', 'local_vid', 'policy', 'remote_vid', 'url']
  1119. @classmethod
  1120. def setUpTestData(cls):
  1121. vlan_translation_policies = (
  1122. VLANTranslationPolicy(
  1123. name='Policy 1',
  1124. description='foobar1',
  1125. ),
  1126. VLANTranslationPolicy(
  1127. name='Policy 2',
  1128. description='foobar2',
  1129. ),
  1130. VLANTranslationPolicy(
  1131. name='Policy 3',
  1132. description='foobar2',
  1133. ),
  1134. )
  1135. VLANTranslationPolicy.objects.bulk_create(vlan_translation_policies)
  1136. vlan_translation_rules = (
  1137. VLANTranslationRule(
  1138. policy=vlan_translation_policies[0],
  1139. local_vid=100,
  1140. remote_vid=200,
  1141. description='foo',
  1142. ),
  1143. VLANTranslationRule(
  1144. policy=vlan_translation_policies[0],
  1145. local_vid=101,
  1146. remote_vid=201,
  1147. description='bar',
  1148. ),
  1149. VLANTranslationRule(
  1150. policy=vlan_translation_policies[1],
  1151. local_vid=102,
  1152. remote_vid=202,
  1153. description='baz',
  1154. ),
  1155. )
  1156. VLANTranslationRule.objects.bulk_create(vlan_translation_rules)
  1157. cls.create_data = [
  1158. {
  1159. 'policy': vlan_translation_policies[0].pk,
  1160. 'local_vid': 300,
  1161. 'remote_vid': 400,
  1162. },
  1163. {
  1164. 'policy': vlan_translation_policies[0].pk,
  1165. 'local_vid': 301,
  1166. 'remote_vid': 401,
  1167. },
  1168. {
  1169. 'policy': vlan_translation_policies[1].pk,
  1170. 'local_vid': 302,
  1171. 'remote_vid': 402,
  1172. },
  1173. ]
  1174. cls.bulk_update_data = {
  1175. 'policy': vlan_translation_policies[2].pk,
  1176. 'description': 'New description',
  1177. }
  1178. class ServiceTemplateTest(APIViewTestCases.APIViewTestCase):
  1179. model = ServiceTemplate
  1180. brief_fields = ['description', 'display', 'id', 'name', 'ports', 'protocol', 'url']
  1181. bulk_update_data = {
  1182. 'description': 'New description',
  1183. }
  1184. graphql_base_name = 'service_template'
  1185. @classmethod
  1186. def setUpTestData(cls):
  1187. service_templates = (
  1188. ServiceTemplate(name='Service Template 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1, 2]),
  1189. ServiceTemplate(name='Service Template 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[3, 4]),
  1190. ServiceTemplate(name='Service Template 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[5, 6]),
  1191. )
  1192. ServiceTemplate.objects.bulk_create(service_templates)
  1193. cls.create_data = [
  1194. {
  1195. 'name': 'Service Template 4',
  1196. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  1197. 'ports': [7, 8],
  1198. },
  1199. {
  1200. 'name': 'Service Template 5',
  1201. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  1202. 'ports': [9, 10],
  1203. },
  1204. {
  1205. 'name': 'Service Template 6',
  1206. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  1207. 'ports': [11, 12],
  1208. },
  1209. ]
  1210. class ServiceTest(APIViewTestCases.APIViewTestCase):
  1211. model = Service
  1212. brief_fields = ['description', 'display', 'id', 'name', 'ports', 'protocol', 'url']
  1213. bulk_update_data = {
  1214. 'description': 'New description',
  1215. }
  1216. graphql_base_name = 'service'
  1217. @classmethod
  1218. def setUpTestData(cls):
  1219. site = Site.objects.create(name='Site 1', slug='site-1')
  1220. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1221. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
  1222. role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
  1223. devices = (
  1224. Device(name='Device 1', site=site, device_type=devicetype, role=role),
  1225. Device(name='Device 2', site=site, device_type=devicetype, role=role),
  1226. )
  1227. Device.objects.bulk_create(devices)
  1228. services = (
  1229. Service(parent=devices[0], name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1]),
  1230. Service(parent=devices[0], name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[2]),
  1231. Service(parent=devices[0], name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[3]),
  1232. )
  1233. Service.objects.bulk_create(services)
  1234. cls.create_data = [
  1235. {
  1236. 'parent_object_id': devices[1].pk,
  1237. 'parent_object_type': 'dcim.device',
  1238. 'name': 'Service 4',
  1239. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  1240. 'ports': [4],
  1241. },
  1242. {
  1243. 'parent_object_id': devices[1].pk,
  1244. 'parent_object_type': 'dcim.device',
  1245. 'name': 'Service 5',
  1246. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  1247. 'ports': [5],
  1248. },
  1249. {
  1250. 'parent_object_id': devices[1].pk,
  1251. 'parent_object_type': 'dcim.device',
  1252. 'name': 'Service 6',
  1253. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  1254. 'ports': [6],
  1255. },
  1256. ]