test_api.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
  1. import json
  2. from django.urls import reverse
  3. from netaddr import IPNetwork
  4. from rest_framework import status
  5. from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
  6. from ipam.choices import *
  7. from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
  8. from utilities.testing import APITestCase, disable_warnings
  9. class AppTest(APITestCase):
  10. def test_root(self):
  11. url = reverse('ipam-api:api-root')
  12. response = self.client.get('{}?format=api'.format(url), **self.header)
  13. self.assertEqual(response.status_code, 200)
  14. class VRFTest(APITestCase):
  15. def setUp(self):
  16. super().setUp()
  17. self.vrf1 = VRF.objects.create(name='Test VRF 1', rd='65000:1')
  18. self.vrf2 = VRF.objects.create(name='Test VRF 2', rd='65000:2')
  19. self.vrf3 = VRF.objects.create(name='Test VRF 3') # No RD
  20. def test_get_vrf(self):
  21. url = reverse('ipam-api:vrf-detail', kwargs={'pk': self.vrf1.pk})
  22. response = self.client.get(url, **self.header)
  23. self.assertEqual(response.data['name'], self.vrf1.name)
  24. def test_list_vrfs(self):
  25. url = reverse('ipam-api:vrf-list')
  26. response = self.client.get(url, **self.header)
  27. self.assertEqual(response.data['count'], 3)
  28. def test_list_vrfs_brief(self):
  29. url = reverse('ipam-api:vrf-list')
  30. response = self.client.get('{}?brief=1'.format(url), **self.header)
  31. self.assertEqual(
  32. sorted(response.data['results'][0]),
  33. ['id', 'name', 'prefix_count', 'rd', 'url']
  34. )
  35. def test_create_vrf(self):
  36. data_list = [
  37. # VRF with RD
  38. {
  39. 'name': 'Test VRF 4',
  40. 'rd': '65000:4',
  41. },
  42. # VRF without RD
  43. {
  44. 'name': 'Test VRF 5',
  45. }
  46. ]
  47. url = reverse('ipam-api:vrf-list')
  48. for data in data_list:
  49. response = self.client.post(url, data, format='json', **self.header)
  50. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  51. vrf = VRF.objects.get(pk=response.data['id'])
  52. self.assertEqual(vrf.name, data['name'])
  53. self.assertEqual(vrf.rd, data['rd'] if 'rd' in data else None)
  54. def test_create_vrf_bulk(self):
  55. data = [
  56. {
  57. 'name': 'Test VRF 4',
  58. 'rd': '65000:4',
  59. },
  60. {
  61. 'name': 'Test VRF 5',
  62. 'rd': '65000:5',
  63. },
  64. {
  65. 'name': 'Test VRF 6',
  66. 'rd': '65000:6',
  67. },
  68. ]
  69. url = reverse('ipam-api:vrf-list')
  70. response = self.client.post(url, data, format='json', **self.header)
  71. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  72. self.assertEqual(VRF.objects.count(), 6)
  73. self.assertEqual(response.data[0]['name'], data[0]['name'])
  74. self.assertEqual(response.data[1]['name'], data[1]['name'])
  75. self.assertEqual(response.data[2]['name'], data[2]['name'])
  76. def test_update_vrf(self):
  77. data = {
  78. 'name': 'Test VRF X',
  79. 'rd': '65000:99',
  80. }
  81. url = reverse('ipam-api:vrf-detail', kwargs={'pk': self.vrf1.pk})
  82. response = self.client.put(url, data, format='json', **self.header)
  83. self.assertHttpStatus(response, status.HTTP_200_OK)
  84. self.assertEqual(VRF.objects.count(), 3)
  85. vrf1 = VRF.objects.get(pk=response.data['id'])
  86. self.assertEqual(vrf1.name, data['name'])
  87. self.assertEqual(vrf1.rd, data['rd'])
  88. def test_delete_vrf(self):
  89. url = reverse('ipam-api:vrf-detail', kwargs={'pk': self.vrf1.pk})
  90. response = self.client.delete(url, **self.header)
  91. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  92. self.assertEqual(VRF.objects.count(), 2)
  93. class RIRTest(APITestCase):
  94. def setUp(self):
  95. super().setUp()
  96. self.rir1 = RIR.objects.create(name='Test RIR 1', slug='test-rir-1')
  97. self.rir2 = RIR.objects.create(name='Test RIR 2', slug='test-rir-2')
  98. self.rir3 = RIR.objects.create(name='Test RIR 3', slug='test-rir-3')
  99. def test_get_rir(self):
  100. url = reverse('ipam-api:rir-detail', kwargs={'pk': self.rir1.pk})
  101. response = self.client.get(url, **self.header)
  102. self.assertEqual(response.data['name'], self.rir1.name)
  103. def test_list_rirs(self):
  104. url = reverse('ipam-api:rir-list')
  105. response = self.client.get(url, **self.header)
  106. self.assertEqual(response.data['count'], 3)
  107. def test_list_rirs_brief(self):
  108. url = reverse('ipam-api:rir-list')
  109. response = self.client.get('{}?brief=1'.format(url), **self.header)
  110. self.assertEqual(
  111. sorted(response.data['results'][0]),
  112. ['aggregate_count', 'id', 'name', 'slug', 'url']
  113. )
  114. def test_create_rir(self):
  115. data = {
  116. 'name': 'Test RIR 4',
  117. 'slug': 'test-rir-4',
  118. }
  119. url = reverse('ipam-api:rir-list')
  120. response = self.client.post(url, data, format='json', **self.header)
  121. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  122. self.assertEqual(RIR.objects.count(), 4)
  123. rir4 = RIR.objects.get(pk=response.data['id'])
  124. self.assertEqual(rir4.name, data['name'])
  125. self.assertEqual(rir4.slug, data['slug'])
  126. def test_create_rir_bulk(self):
  127. data = [
  128. {
  129. 'name': 'Test RIR 4',
  130. 'slug': 'test-rir-4',
  131. },
  132. {
  133. 'name': 'Test RIR 5',
  134. 'slug': 'test-rir-5',
  135. },
  136. {
  137. 'name': 'Test RIR 6',
  138. 'slug': 'test-rir-6',
  139. },
  140. ]
  141. url = reverse('ipam-api:rir-list')
  142. response = self.client.post(url, data, format='json', **self.header)
  143. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  144. self.assertEqual(RIR.objects.count(), 6)
  145. self.assertEqual(response.data[0]['name'], data[0]['name'])
  146. self.assertEqual(response.data[1]['name'], data[1]['name'])
  147. self.assertEqual(response.data[2]['name'], data[2]['name'])
  148. def test_update_rir(self):
  149. data = {
  150. 'name': 'Test RIR X',
  151. 'slug': 'test-rir-x',
  152. }
  153. url = reverse('ipam-api:rir-detail', kwargs={'pk': self.rir1.pk})
  154. response = self.client.put(url, data, format='json', **self.header)
  155. self.assertHttpStatus(response, status.HTTP_200_OK)
  156. self.assertEqual(RIR.objects.count(), 3)
  157. rir1 = RIR.objects.get(pk=response.data['id'])
  158. self.assertEqual(rir1.name, data['name'])
  159. self.assertEqual(rir1.slug, data['slug'])
  160. def test_delete_rir(self):
  161. url = reverse('ipam-api:rir-detail', kwargs={'pk': self.rir1.pk})
  162. response = self.client.delete(url, **self.header)
  163. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  164. self.assertEqual(RIR.objects.count(), 2)
  165. class AggregateTest(APITestCase):
  166. def setUp(self):
  167. super().setUp()
  168. self.rir1 = RIR.objects.create(name='Test RIR 1', slug='test-rir-1')
  169. self.rir2 = RIR.objects.create(name='Test RIR 2', slug='test-rir-2')
  170. self.aggregate1 = Aggregate.objects.create(prefix=IPNetwork('10.0.0.0/8'), rir=self.rir1)
  171. self.aggregate2 = Aggregate.objects.create(prefix=IPNetwork('172.16.0.0/12'), rir=self.rir1)
  172. self.aggregate3 = Aggregate.objects.create(prefix=IPNetwork('192.168.0.0/16'), rir=self.rir1)
  173. def test_get_aggregate(self):
  174. url = reverse('ipam-api:aggregate-detail', kwargs={'pk': self.aggregate1.pk})
  175. response = self.client.get(url, **self.header)
  176. self.assertEqual(response.data['family']['value'], 4)
  177. self.assertEqual(response.data['prefix'], str(self.aggregate1.prefix))
  178. def test_list_aggregates(self):
  179. url = reverse('ipam-api:aggregate-list')
  180. response = self.client.get(url, **self.header)
  181. self.assertEqual(response.data['count'], 3)
  182. def test_list_aggregates_brief(self):
  183. url = reverse('ipam-api:aggregate-list')
  184. response = self.client.get('{}?brief=1'.format(url), **self.header)
  185. self.assertEqual(
  186. sorted(response.data['results'][0]),
  187. ['family', 'id', 'prefix', 'url']
  188. )
  189. def test_create_aggregate(self):
  190. data = {
  191. 'prefix': '192.0.2.0/24',
  192. 'rir': self.rir1.pk,
  193. }
  194. url = reverse('ipam-api:aggregate-list')
  195. response = self.client.post(url, data, format='json', **self.header)
  196. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  197. self.assertEqual(Aggregate.objects.count(), 4)
  198. aggregate4 = Aggregate.objects.get(pk=response.data['id'])
  199. self.assertEqual(str(aggregate4.prefix), data['prefix'])
  200. self.assertEqual(aggregate4.rir_id, data['rir'])
  201. def test_create_aggregate_bulk(self):
  202. data = [
  203. {
  204. 'prefix': '100.0.0.0/8',
  205. 'rir': self.rir1.pk,
  206. },
  207. {
  208. 'prefix': '101.0.0.0/8',
  209. 'rir': self.rir1.pk,
  210. },
  211. {
  212. 'prefix': '102.0.0.0/8',
  213. 'rir': self.rir1.pk,
  214. },
  215. ]
  216. url = reverse('ipam-api:aggregate-list')
  217. response = self.client.post(url, data, format='json', **self.header)
  218. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  219. self.assertEqual(Aggregate.objects.count(), 6)
  220. self.assertEqual(response.data[0]['prefix'], data[0]['prefix'])
  221. self.assertEqual(response.data[1]['prefix'], data[1]['prefix'])
  222. self.assertEqual(response.data[2]['prefix'], data[2]['prefix'])
  223. def test_update_aggregate(self):
  224. data = {
  225. 'prefix': '11.0.0.0/8',
  226. 'rir': self.rir2.pk,
  227. }
  228. url = reverse('ipam-api:aggregate-detail', kwargs={'pk': self.aggregate1.pk})
  229. response = self.client.put(url, data, format='json', **self.header)
  230. self.assertHttpStatus(response, status.HTTP_200_OK)
  231. self.assertEqual(Aggregate.objects.count(), 3)
  232. aggregate1 = Aggregate.objects.get(pk=response.data['id'])
  233. self.assertEqual(str(aggregate1.prefix), data['prefix'])
  234. self.assertEqual(aggregate1.rir_id, data['rir'])
  235. def test_delete_aggregate(self):
  236. url = reverse('ipam-api:aggregate-detail', kwargs={'pk': self.aggregate1.pk})
  237. response = self.client.delete(url, **self.header)
  238. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  239. self.assertEqual(Aggregate.objects.count(), 2)
  240. class RoleTest(APITestCase):
  241. def setUp(self):
  242. super().setUp()
  243. self.role1 = Role.objects.create(name='Test Role 1', slug='test-role-1')
  244. self.role2 = Role.objects.create(name='Test Role 2', slug='test-role-2')
  245. self.role3 = Role.objects.create(name='Test Role 3', slug='test-role-3')
  246. def test_get_role(self):
  247. url = reverse('ipam-api:role-detail', kwargs={'pk': self.role1.pk})
  248. response = self.client.get(url, **self.header)
  249. self.assertEqual(response.data['name'], self.role1.name)
  250. def test_list_roles(self):
  251. url = reverse('ipam-api:role-list')
  252. response = self.client.get(url, **self.header)
  253. self.assertEqual(response.data['count'], 3)
  254. def test_list_roles_brief(self):
  255. url = reverse('ipam-api:role-list')
  256. response = self.client.get('{}?brief=1'.format(url), **self.header)
  257. self.assertEqual(
  258. sorted(response.data['results'][0]),
  259. ['id', 'name', 'prefix_count', 'slug', 'url', 'vlan_count']
  260. )
  261. def test_create_role(self):
  262. data = {
  263. 'name': 'Test Role 4',
  264. 'slug': 'test-role-4',
  265. }
  266. url = reverse('ipam-api:role-list')
  267. response = self.client.post(url, data, format='json', **self.header)
  268. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  269. self.assertEqual(Role.objects.count(), 4)
  270. role4 = Role.objects.get(pk=response.data['id'])
  271. self.assertEqual(role4.name, data['name'])
  272. self.assertEqual(role4.slug, data['slug'])
  273. def test_create_role_bulk(self):
  274. data = [
  275. {
  276. 'name': 'Test Role 4',
  277. 'slug': 'test-role-4',
  278. },
  279. {
  280. 'name': 'Test Role 5',
  281. 'slug': 'test-role-5',
  282. },
  283. {
  284. 'name': 'Test Role 6',
  285. 'slug': 'test-role-6',
  286. },
  287. ]
  288. url = reverse('ipam-api:role-list')
  289. response = self.client.post(url, data, format='json', **self.header)
  290. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  291. self.assertEqual(Role.objects.count(), 6)
  292. self.assertEqual(response.data[0]['name'], data[0]['name'])
  293. self.assertEqual(response.data[1]['name'], data[1]['name'])
  294. self.assertEqual(response.data[2]['name'], data[2]['name'])
  295. def test_update_role(self):
  296. data = {
  297. 'name': 'Test Role X',
  298. 'slug': 'test-role-x',
  299. }
  300. url = reverse('ipam-api:role-detail', kwargs={'pk': self.role1.pk})
  301. response = self.client.put(url, data, format='json', **self.header)
  302. self.assertHttpStatus(response, status.HTTP_200_OK)
  303. self.assertEqual(Role.objects.count(), 3)
  304. role1 = Role.objects.get(pk=response.data['id'])
  305. self.assertEqual(role1.name, data['name'])
  306. self.assertEqual(role1.slug, data['slug'])
  307. def test_delete_role(self):
  308. url = reverse('ipam-api:role-detail', kwargs={'pk': self.role1.pk})
  309. response = self.client.delete(url, **self.header)
  310. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  311. self.assertEqual(Role.objects.count(), 2)
  312. class PrefixTest(APITestCase):
  313. def setUp(self):
  314. super().setUp()
  315. self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
  316. self.vrf1 = VRF.objects.create(name='Test VRF 1', rd='65000:1')
  317. self.vlan1 = VLAN.objects.create(vid=1, name='Test VLAN 1')
  318. self.role1 = Role.objects.create(name='Test Role 1', slug='test-role-1')
  319. self.prefix1 = Prefix.objects.create(prefix=IPNetwork('192.168.1.0/24'))
  320. self.prefix2 = Prefix.objects.create(prefix=IPNetwork('192.168.2.0/24'))
  321. self.prefix3 = Prefix.objects.create(prefix=IPNetwork('192.168.3.0/24'))
  322. def test_get_prefix(self):
  323. url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefix1.pk})
  324. response = self.client.get(url, **self.header)
  325. self.assertEqual(response.data['family']['value'], 4)
  326. self.assertEqual(response.data['prefix'], str(self.prefix1.prefix))
  327. def test_list_prefixes(self):
  328. url = reverse('ipam-api:prefix-list')
  329. response = self.client.get(url, **self.header)
  330. self.assertEqual(response.data['count'], 3)
  331. def test_list_prefixes_brief(self):
  332. url = reverse('ipam-api:prefix-list')
  333. response = self.client.get('{}?brief=1'.format(url), **self.header)
  334. self.assertEqual(
  335. sorted(response.data['results'][0]),
  336. ['family', 'id', 'prefix', 'url']
  337. )
  338. def test_create_prefix(self):
  339. data = {
  340. 'prefix': '192.168.4.0/24',
  341. 'site': self.site1.pk,
  342. 'vrf': self.vrf1.pk,
  343. 'vlan': self.vlan1.pk,
  344. 'role': self.role1.pk,
  345. }
  346. url = reverse('ipam-api:prefix-list')
  347. response = self.client.post(url, data, format='json', **self.header)
  348. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  349. self.assertEqual(Prefix.objects.count(), 4)
  350. prefix4 = Prefix.objects.get(pk=response.data['id'])
  351. self.assertEqual(str(prefix4.prefix), data['prefix'])
  352. self.assertEqual(prefix4.site_id, data['site'])
  353. self.assertEqual(prefix4.vrf_id, data['vrf'])
  354. self.assertEqual(prefix4.vlan_id, data['vlan'])
  355. self.assertEqual(prefix4.role_id, data['role'])
  356. def test_create_prefix_bulk(self):
  357. data = [
  358. {
  359. 'prefix': '10.0.1.0/24',
  360. },
  361. {
  362. 'prefix': '10.0.2.0/24',
  363. },
  364. {
  365. 'prefix': '10.0.3.0/24',
  366. },
  367. ]
  368. url = reverse('ipam-api:prefix-list')
  369. response = self.client.post(url, data, format='json', **self.header)
  370. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  371. self.assertEqual(Prefix.objects.count(), 6)
  372. self.assertEqual(response.data[0]['prefix'], data[0]['prefix'])
  373. self.assertEqual(response.data[1]['prefix'], data[1]['prefix'])
  374. self.assertEqual(response.data[2]['prefix'], data[2]['prefix'])
  375. def test_update_prefix(self):
  376. data = {
  377. 'prefix': '192.168.99.0/24',
  378. 'site': self.site1.pk,
  379. 'vrf': self.vrf1.pk,
  380. 'vlan': self.vlan1.pk,
  381. 'role': self.role1.pk,
  382. }
  383. url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefix1.pk})
  384. response = self.client.put(url, data, format='json', **self.header)
  385. self.assertHttpStatus(response, status.HTTP_200_OK)
  386. self.assertEqual(Prefix.objects.count(), 3)
  387. prefix1 = Prefix.objects.get(pk=response.data['id'])
  388. self.assertEqual(str(prefix1.prefix), data['prefix'])
  389. self.assertEqual(prefix1.site_id, data['site'])
  390. self.assertEqual(prefix1.vrf_id, data['vrf'])
  391. self.assertEqual(prefix1.vlan_id, data['vlan'])
  392. self.assertEqual(prefix1.role_id, data['role'])
  393. def test_delete_prefix(self):
  394. url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefix1.pk})
  395. response = self.client.delete(url, **self.header)
  396. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  397. self.assertEqual(Prefix.objects.count(), 2)
  398. def test_list_available_prefixes(self):
  399. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
  400. Prefix.objects.create(prefix=IPNetwork('192.0.2.64/26'))
  401. Prefix.objects.create(prefix=IPNetwork('192.0.2.192/27'))
  402. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  403. # Retrieve all available IPs
  404. response = self.client.get(url, **self.header)
  405. available_prefixes = ['192.0.2.0/26', '192.0.2.128/26', '192.0.2.224/27']
  406. for i, p in enumerate(response.data):
  407. self.assertEqual(p['prefix'], available_prefixes[i])
  408. def test_create_single_available_prefix(self):
  409. vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
  410. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), vrf=vrf, is_pool=True)
  411. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  412. # Create four available prefixes with individual requests
  413. prefixes_to_be_created = [
  414. '192.0.2.0/30',
  415. '192.0.2.4/30',
  416. '192.0.2.8/30',
  417. '192.0.2.12/30',
  418. ]
  419. for i in range(4):
  420. data = {
  421. 'prefix_length': 30,
  422. 'description': 'Test Prefix {}'.format(i + 1)
  423. }
  424. response = self.client.post(url, data, format='json', **self.header)
  425. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  426. self.assertEqual(response.data['prefix'], prefixes_to_be_created[i])
  427. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  428. self.assertEqual(response.data['description'], data['description'])
  429. # Try to create one more prefix
  430. response = self.client.post(url, {'prefix_length': 30}, format='json', **self.header)
  431. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  432. self.assertIn('detail', response.data)
  433. # Try to create invalid prefix type
  434. response = self.client.post(url, {'prefix_length': '30'}, format='json', **self.header)
  435. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  436. self.assertIn('prefix_length', response.data[0])
  437. def test_create_multiple_available_prefixes(self):
  438. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), is_pool=True)
  439. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  440. # Try to create five /30s (only four are available)
  441. data = [
  442. {'prefix_length': 30, 'description': 'Test Prefix 1'},
  443. {'prefix_length': 30, 'description': 'Test Prefix 2'},
  444. {'prefix_length': 30, 'description': 'Test Prefix 3'},
  445. {'prefix_length': 30, 'description': 'Test Prefix 4'},
  446. {'prefix_length': 30, 'description': 'Test Prefix 5'},
  447. ]
  448. response = self.client.post(url, data, format='json', **self.header)
  449. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  450. self.assertIn('detail', response.data)
  451. # Verify that no prefixes were created (the entire /28 is still available)
  452. response = self.client.get(url, **self.header)
  453. self.assertEqual(response.data[0]['prefix'], '192.0.2.0/28')
  454. # Create four /30s in a single request
  455. response = self.client.post(url, data[:4], format='json', **self.header)
  456. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  457. self.assertEqual(len(response.data), 4)
  458. def test_list_available_ips(self):
  459. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
  460. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  461. # Retrieve all available IPs
  462. response = self.client.get(url, **self.header)
  463. self.assertEqual(len(response.data), 8) # 8 because prefix.is_pool = True
  464. # Change the prefix to not be a pool and try again
  465. prefix.is_pool = False
  466. prefix.save()
  467. response = self.client.get(url, **self.header)
  468. self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.is_pool = False
  469. def test_create_single_available_ip(self):
  470. vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
  471. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), vrf=vrf, is_pool=True)
  472. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  473. # Create all four available IPs with individual requests
  474. for i in range(1, 5):
  475. data = {
  476. 'description': 'Test IP {}'.format(i)
  477. }
  478. response = self.client.post(url, data, format='json', **self.header)
  479. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  480. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  481. self.assertEqual(response.data['description'], data['description'])
  482. # Try to create one more IP
  483. response = self.client.post(url, {}, **self.header)
  484. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  485. self.assertIn('detail', response.data)
  486. def test_create_multiple_available_ips(self):
  487. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
  488. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  489. # Try to create nine IPs (only eight are available)
  490. data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 10)] # 9 IPs
  491. response = self.client.post(url, data, format='json', **self.header)
  492. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  493. self.assertIn('detail', response.data)
  494. # Verify that no IPs were created (eight are still available)
  495. response = self.client.get(url, **self.header)
  496. self.assertEqual(len(response.data), 8)
  497. # Create all eight available IPs in a single request
  498. data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 9)] # 8 IPs
  499. response = self.client.post(url, data, format='json', **self.header)
  500. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  501. self.assertEqual(len(response.data), 8)
  502. class IPAddressTest(APITestCase):
  503. def setUp(self):
  504. super().setUp()
  505. self.vrf1 = VRF.objects.create(name='Test VRF 1', rd='65000:1')
  506. self.ipaddress1 = IPAddress.objects.create(address=IPNetwork('192.168.0.1/24'))
  507. self.ipaddress2 = IPAddress.objects.create(address=IPNetwork('192.168.0.2/24'))
  508. self.ipaddress3 = IPAddress.objects.create(address=IPNetwork('192.168.0.3/24'))
  509. def test_get_ipaddress(self):
  510. url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': self.ipaddress1.pk})
  511. response = self.client.get(url, **self.header)
  512. self.assertEqual(response.data['family']['value'], 4)
  513. self.assertEqual(response.data['address'], str(self.ipaddress1.address))
  514. def test_list_ipaddresss(self):
  515. url = reverse('ipam-api:ipaddress-list')
  516. response = self.client.get(url, **self.header)
  517. self.assertEqual(response.data['count'], 3)
  518. def test_list_ipaddresses_brief(self):
  519. url = reverse('ipam-api:ipaddress-list')
  520. response = self.client.get('{}?brief=1'.format(url), **self.header)
  521. self.assertEqual(
  522. sorted(response.data['results'][0]),
  523. ['address', 'family', 'id', 'url']
  524. )
  525. def test_create_ipaddress(self):
  526. data = {
  527. 'address': '192.168.0.4/24',
  528. 'vrf': self.vrf1.pk,
  529. }
  530. url = reverse('ipam-api:ipaddress-list')
  531. response = self.client.post(url, data, format='json', **self.header)
  532. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  533. self.assertEqual(IPAddress.objects.count(), 4)
  534. ipaddress4 = IPAddress.objects.get(pk=response.data['id'])
  535. self.assertEqual(str(ipaddress4.address), data['address'])
  536. self.assertEqual(ipaddress4.vrf_id, data['vrf'])
  537. def test_create_ipaddress_bulk(self):
  538. data = [
  539. {
  540. 'address': '192.168.0.4/24',
  541. },
  542. {
  543. 'address': '192.168.0.5/24',
  544. },
  545. {
  546. 'address': '192.168.0.6/24',
  547. },
  548. ]
  549. url = reverse('ipam-api:ipaddress-list')
  550. response = self.client.post(url, data, format='json', **self.header)
  551. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  552. self.assertEqual(IPAddress.objects.count(), 6)
  553. self.assertEqual(response.data[0]['address'], data[0]['address'])
  554. self.assertEqual(response.data[1]['address'], data[1]['address'])
  555. self.assertEqual(response.data[2]['address'], data[2]['address'])
  556. def test_update_ipaddress(self):
  557. data = {
  558. 'address': '192.168.0.99/24',
  559. 'vrf': self.vrf1.pk,
  560. }
  561. url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': self.ipaddress1.pk})
  562. response = self.client.put(url, data, format='json', **self.header)
  563. self.assertHttpStatus(response, status.HTTP_200_OK)
  564. self.assertEqual(IPAddress.objects.count(), 3)
  565. ipaddress1 = IPAddress.objects.get(pk=response.data['id'])
  566. self.assertEqual(str(ipaddress1.address), data['address'])
  567. self.assertEqual(ipaddress1.vrf_id, data['vrf'])
  568. def test_delete_ipaddress(self):
  569. url = reverse('ipam-api:ipaddress-detail', kwargs={'pk': self.ipaddress1.pk})
  570. response = self.client.delete(url, **self.header)
  571. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  572. self.assertEqual(IPAddress.objects.count(), 2)
  573. class VLANGroupTest(APITestCase):
  574. def setUp(self):
  575. super().setUp()
  576. self.vlangroup1 = VLANGroup.objects.create(name='Test VLAN Group 1', slug='test-vlan-group-1')
  577. self.vlangroup2 = VLANGroup.objects.create(name='Test VLAN Group 2', slug='test-vlan-group-2')
  578. self.vlangroup3 = VLANGroup.objects.create(name='Test VLAN Group 3', slug='test-vlan-group-3')
  579. def test_get_vlangroup(self):
  580. url = reverse('ipam-api:vlangroup-detail', kwargs={'pk': self.vlangroup1.pk})
  581. response = self.client.get(url, **self.header)
  582. self.assertEqual(response.data['name'], self.vlangroup1.name)
  583. def test_list_vlangroups(self):
  584. url = reverse('ipam-api:vlangroup-list')
  585. response = self.client.get(url, **self.header)
  586. self.assertEqual(response.data['count'], 3)
  587. def test_list_vlangroups_brief(self):
  588. url = reverse('ipam-api:vlangroup-list')
  589. response = self.client.get('{}?brief=1'.format(url), **self.header)
  590. self.assertEqual(
  591. sorted(response.data['results'][0]),
  592. ['id', 'name', 'slug', 'url', 'vlan_count']
  593. )
  594. def test_create_vlangroup(self):
  595. data = {
  596. 'name': 'Test VLAN Group 4',
  597. 'slug': 'test-vlan-group-4',
  598. }
  599. url = reverse('ipam-api:vlangroup-list')
  600. response = self.client.post(url, data, format='json', **self.header)
  601. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  602. self.assertEqual(VLANGroup.objects.count(), 4)
  603. vlangroup4 = VLANGroup.objects.get(pk=response.data['id'])
  604. self.assertEqual(vlangroup4.name, data['name'])
  605. self.assertEqual(vlangroup4.slug, data['slug'])
  606. def test_create_vlangroup_bulk(self):
  607. data = [
  608. {
  609. 'name': 'Test VLAN Group 4',
  610. 'slug': 'test-vlan-group-4',
  611. },
  612. {
  613. 'name': 'Test VLAN Group 5',
  614. 'slug': 'test-vlan-group-5',
  615. },
  616. {
  617. 'name': 'Test VLAN Group 6',
  618. 'slug': 'test-vlan-group-6',
  619. },
  620. ]
  621. url = reverse('ipam-api:vlangroup-list')
  622. response = self.client.post(url, data, format='json', **self.header)
  623. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  624. self.assertEqual(VLANGroup.objects.count(), 6)
  625. self.assertEqual(response.data[0]['name'], data[0]['name'])
  626. self.assertEqual(response.data[1]['name'], data[1]['name'])
  627. self.assertEqual(response.data[2]['name'], data[2]['name'])
  628. def test_update_vlangroup(self):
  629. data = {
  630. 'name': 'Test VLAN Group X',
  631. 'slug': 'test-vlan-group-x',
  632. }
  633. url = reverse('ipam-api:vlangroup-detail', kwargs={'pk': self.vlangroup1.pk})
  634. response = self.client.put(url, data, format='json', **self.header)
  635. self.assertHttpStatus(response, status.HTTP_200_OK)
  636. self.assertEqual(VLANGroup.objects.count(), 3)
  637. vlangroup1 = VLANGroup.objects.get(pk=response.data['id'])
  638. self.assertEqual(vlangroup1.name, data['name'])
  639. self.assertEqual(vlangroup1.slug, data['slug'])
  640. def test_delete_vlangroup(self):
  641. url = reverse('ipam-api:vlangroup-detail', kwargs={'pk': self.vlangroup1.pk})
  642. response = self.client.delete(url, **self.header)
  643. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  644. self.assertEqual(VLANGroup.objects.count(), 2)
  645. class VLANTest(APITestCase):
  646. def setUp(self):
  647. super().setUp()
  648. self.vlan1 = VLAN.objects.create(vid=1, name='Test VLAN 1')
  649. self.vlan2 = VLAN.objects.create(vid=2, name='Test VLAN 2')
  650. self.vlan3 = VLAN.objects.create(vid=3, name='Test VLAN 3')
  651. self.prefix1 = Prefix.objects.create(prefix=IPNetwork('192.168.1.0/24'))
  652. def test_get_vlan(self):
  653. url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
  654. response = self.client.get(url, **self.header)
  655. self.assertEqual(response.data['name'], self.vlan1.name)
  656. def test_list_vlans(self):
  657. url = reverse('ipam-api:vlan-list')
  658. response = self.client.get(url, **self.header)
  659. self.assertEqual(response.data['count'], 3)
  660. def test_list_vlans_brief(self):
  661. url = reverse('ipam-api:vlan-list')
  662. response = self.client.get('{}?brief=1'.format(url), **self.header)
  663. self.assertEqual(
  664. sorted(response.data['results'][0]),
  665. ['display_name', 'id', 'name', 'url', 'vid']
  666. )
  667. def test_create_vlan(self):
  668. data = {
  669. 'vid': 4,
  670. 'name': 'Test VLAN 4',
  671. }
  672. url = reverse('ipam-api:vlan-list')
  673. response = self.client.post(url, data, format='json', **self.header)
  674. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  675. self.assertEqual(VLAN.objects.count(), 4)
  676. vlan4 = VLAN.objects.get(pk=response.data['id'])
  677. self.assertEqual(vlan4.vid, data['vid'])
  678. self.assertEqual(vlan4.name, data['name'])
  679. def test_create_vlan_bulk(self):
  680. data = [
  681. {
  682. 'vid': 4,
  683. 'name': 'Test VLAN 4',
  684. },
  685. {
  686. 'vid': 5,
  687. 'name': 'Test VLAN 5',
  688. },
  689. {
  690. 'vid': 6,
  691. 'name': 'Test VLAN 6',
  692. },
  693. ]
  694. url = reverse('ipam-api:vlan-list')
  695. response = self.client.post(url, data, format='json', **self.header)
  696. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  697. self.assertEqual(VLAN.objects.count(), 6)
  698. self.assertEqual(response.data[0]['name'], data[0]['name'])
  699. self.assertEqual(response.data[1]['name'], data[1]['name'])
  700. self.assertEqual(response.data[2]['name'], data[2]['name'])
  701. def test_update_vlan(self):
  702. data = {
  703. 'vid': 99,
  704. 'name': 'Test VLAN X',
  705. }
  706. url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
  707. response = self.client.put(url, data, format='json', **self.header)
  708. self.assertHttpStatus(response, status.HTTP_200_OK)
  709. self.assertEqual(VLAN.objects.count(), 3)
  710. vlan1 = VLAN.objects.get(pk=response.data['id'])
  711. self.assertEqual(vlan1.vid, data['vid'])
  712. self.assertEqual(vlan1.name, data['name'])
  713. def test_delete_vlan(self):
  714. url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
  715. response = self.client.delete(url, **self.header)
  716. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  717. self.assertEqual(VLAN.objects.count(), 2)
  718. def test_delete_vlan_with_prefix(self):
  719. self.prefix1.vlan = self.vlan1
  720. self.prefix1.save()
  721. url = reverse('ipam-api:vlan-detail', kwargs={'pk': self.vlan1.pk})
  722. with disable_warnings('django.request'):
  723. response = self.client.delete(url, **self.header)
  724. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  725. content = json.loads(response.content.decode('utf-8'))
  726. self.assertIn('detail', content)
  727. self.assertTrue(content['detail'].startswith('Unable to delete object.'))
  728. class ServiceTest(APITestCase):
  729. def setUp(self):
  730. super().setUp()
  731. site = Site.objects.create(name='Test Site 1', slug='test-site-1')
  732. manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
  733. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Test Device Type 1')
  734. devicerole = DeviceRole.objects.create(name='Test Device Role 1', slug='test-device-role-1')
  735. self.device1 = Device.objects.create(
  736. name='Test Device 1', site=site, device_type=devicetype, device_role=devicerole
  737. )
  738. self.device2 = Device.objects.create(
  739. name='Test Device 2', site=site, device_type=devicetype, device_role=devicerole
  740. )
  741. self.service1 = Service.objects.create(
  742. device=self.device1, name='Test Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=1
  743. )
  744. self.service1 = Service.objects.create(
  745. device=self.device1, name='Test Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=2
  746. )
  747. self.service1 = Service.objects.create(
  748. device=self.device1, name='Test Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=3
  749. )
  750. def test_get_service(self):
  751. url = reverse('ipam-api:service-detail', kwargs={'pk': self.service1.pk})
  752. response = self.client.get(url, **self.header)
  753. self.assertEqual(response.data['name'], self.service1.name)
  754. def test_list_services(self):
  755. url = reverse('ipam-api:service-list')
  756. response = self.client.get(url, **self.header)
  757. self.assertEqual(response.data['count'], 3)
  758. def test_create_service(self):
  759. data = {
  760. 'device': self.device1.pk,
  761. 'name': 'Test Service 4',
  762. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  763. 'port': 4,
  764. 'tags': ['Foo', 'Bar'],
  765. }
  766. url = reverse('ipam-api:service-list')
  767. response = self.client.post(url, data, format='json', **self.header)
  768. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  769. self.assertEqual(Service.objects.count(), 4)
  770. service4 = Service.objects.get(pk=response.data['id'])
  771. self.assertEqual(service4.device_id, data['device'])
  772. self.assertEqual(service4.name, data['name'])
  773. self.assertEqual(service4.protocol, data['protocol'])
  774. self.assertEqual(service4.port, data['port'])
  775. tags = [tag.name for tag in service4.tags.all()]
  776. self.assertEqual(sorted(tags), sorted(data['tags']))
  777. def test_create_service_bulk(self):
  778. data = [
  779. {
  780. 'device': self.device1.pk,
  781. 'name': 'Test Service 4',
  782. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  783. 'port': 4,
  784. },
  785. {
  786. 'device': self.device1.pk,
  787. 'name': 'Test Service 5',
  788. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  789. 'port': 5,
  790. },
  791. {
  792. 'device': self.device1.pk,
  793. 'name': 'Test Service 6',
  794. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  795. 'port': 6,
  796. },
  797. ]
  798. url = reverse('ipam-api:service-list')
  799. response = self.client.post(url, data, format='json', **self.header)
  800. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  801. self.assertEqual(Service.objects.count(), 6)
  802. self.assertEqual(response.data[0]['name'], data[0]['name'])
  803. self.assertEqual(response.data[1]['name'], data[1]['name'])
  804. self.assertEqual(response.data[2]['name'], data[2]['name'])
  805. def test_update_service(self):
  806. data = {
  807. 'device': self.device2.pk,
  808. 'name': 'Test Service X',
  809. 'protocol': ServiceProtocolChoices.PROTOCOL_UDP,
  810. 'port': 99,
  811. }
  812. url = reverse('ipam-api:service-detail', kwargs={'pk': self.service1.pk})
  813. response = self.client.put(url, data, format='json', **self.header)
  814. self.assertHttpStatus(response, status.HTTP_200_OK)
  815. self.assertEqual(Service.objects.count(), 3)
  816. service1 = Service.objects.get(pk=response.data['id'])
  817. self.assertEqual(service1.device_id, data['device'])
  818. self.assertEqual(service1.name, data['name'])
  819. self.assertEqual(service1.protocol, data['protocol'])
  820. self.assertEqual(service1.port, data['port'])
  821. def test_delete_service(self):
  822. url = reverse('ipam-api:service-detail', kwargs={'pk': self.service1.pk})
  823. response = self.client.delete(url, **self.header)
  824. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  825. self.assertEqual(Service.objects.count(), 2)