test_api.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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, APIViewTestCases, 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(APIViewTestCases.APIViewTestCase):
  15. model = VRF
  16. brief_fields = ['id', 'name', 'prefix_count', 'rd', 'url']
  17. create_data = [
  18. {
  19. 'name': 'VRF 4',
  20. 'rd': '65000:4',
  21. },
  22. {
  23. 'name': 'VRF 5',
  24. 'rd': '65000:5',
  25. },
  26. {
  27. 'name': 'VRF 6',
  28. 'rd': '65000:6',
  29. },
  30. ]
  31. @classmethod
  32. def setUpTestData(cls):
  33. vrfs = (
  34. VRF(name='VRF 1', rd='65000:1'),
  35. VRF(name='VRF 2', rd='65000:2'),
  36. VRF(name='VRF 3'), # No RD
  37. )
  38. VRF.objects.bulk_create(vrfs)
  39. class RIRTest(APIViewTestCases.APIViewTestCase):
  40. model = RIR
  41. brief_fields = ['aggregate_count', 'id', 'name', 'slug', 'url']
  42. create_data = [
  43. {
  44. 'name': 'RIR 4',
  45. 'slug': 'rir-4',
  46. },
  47. {
  48. 'name': 'RIR 5',
  49. 'slug': 'rir-5',
  50. },
  51. {
  52. 'name': 'RIR 6',
  53. 'slug': 'rir-6',
  54. },
  55. ]
  56. @classmethod
  57. def setUpTestData(cls):
  58. rirs = (
  59. RIR(name='RIR 1', slug='rir-1'),
  60. RIR(name='RIR 2', slug='rir-2'),
  61. RIR(name='RIR 3', slug='rir-3'),
  62. )
  63. RIR.objects.bulk_create(rirs)
  64. class AggregateTest(APIViewTestCases.APIViewTestCase):
  65. model = Aggregate
  66. brief_fields = ['family', 'id', 'prefix', 'url']
  67. @classmethod
  68. def setUpTestData(cls):
  69. rirs = (
  70. RIR(name='RIR 1', slug='rir-1'),
  71. RIR(name='RIR 2', slug='rir-2'),
  72. )
  73. RIR.objects.bulk_create(rirs)
  74. aggregates = (
  75. Aggregate(prefix=IPNetwork('10.0.0.0/8'), rir=rirs[0]),
  76. Aggregate(prefix=IPNetwork('172.16.0.0/12'), rir=rirs[0]),
  77. Aggregate(prefix=IPNetwork('192.168.0.0/16'), rir=rirs[0]),
  78. )
  79. Aggregate.objects.bulk_create(aggregates)
  80. cls.create_data = [
  81. {
  82. 'prefix': '100.0.0.0/8',
  83. 'rir': rirs[1].pk,
  84. },
  85. {
  86. 'prefix': '101.0.0.0/8',
  87. 'rir': rirs[1].pk,
  88. },
  89. {
  90. 'prefix': '102.0.0.0/8',
  91. 'rir': rirs[1].pk,
  92. },
  93. ]
  94. class RoleTest(APIViewTestCases.APIViewTestCase):
  95. model = Role
  96. brief_fields = ['id', 'name', 'prefix_count', 'slug', 'url', 'vlan_count']
  97. create_data = [
  98. {
  99. 'name': 'Role 4',
  100. 'slug': 'role-4',
  101. },
  102. {
  103. 'name': 'Role 5',
  104. 'slug': 'role-5',
  105. },
  106. {
  107. 'name': 'Role 6',
  108. 'slug': 'role-6',
  109. },
  110. ]
  111. @classmethod
  112. def setUpTestData(cls):
  113. roles = (
  114. Role(name='Role 1', slug='role-1'),
  115. Role(name='Role 2', slug='role-2'),
  116. Role(name='Role 3', slug='role-3'),
  117. )
  118. Role.objects.bulk_create(roles)
  119. class PrefixTest(APIViewTestCases.APIViewTestCase):
  120. model = Prefix
  121. brief_fields = ['family', 'id', 'prefix', 'url']
  122. create_data = [
  123. {
  124. 'prefix': '192.168.4.0/24',
  125. },
  126. {
  127. 'prefix': '192.168.5.0/24',
  128. },
  129. {
  130. 'prefix': '192.168.6.0/24',
  131. },
  132. ]
  133. @classmethod
  134. def setUpTestData(cls):
  135. prefixes = (
  136. Prefix(prefix=IPNetwork('192.168.1.0/24')),
  137. Prefix(prefix=IPNetwork('192.168.2.0/24')),
  138. Prefix(prefix=IPNetwork('192.168.3.0/24')),
  139. )
  140. Prefix.objects.bulk_create(prefixes)
  141. def test_list_available_prefixes(self):
  142. """
  143. Test retrieval of all available prefixes within a parent prefix.
  144. """
  145. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
  146. Prefix.objects.create(prefix=IPNetwork('192.0.2.64/26'))
  147. Prefix.objects.create(prefix=IPNetwork('192.0.2.192/27'))
  148. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  149. # Retrieve all available IPs
  150. response = self.client.get(url, **self.header)
  151. available_prefixes = ['192.0.2.0/26', '192.0.2.128/26', '192.0.2.224/27']
  152. for i, p in enumerate(response.data):
  153. self.assertEqual(p['prefix'], available_prefixes[i])
  154. def test_create_single_available_prefix(self):
  155. """
  156. Test retrieval of the first available prefix within a parent prefix.
  157. """
  158. vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
  159. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), vrf=vrf, is_pool=True)
  160. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  161. # Create four available prefixes with individual requests
  162. prefixes_to_be_created = [
  163. '192.0.2.0/30',
  164. '192.0.2.4/30',
  165. '192.0.2.8/30',
  166. '192.0.2.12/30',
  167. ]
  168. for i in range(4):
  169. data = {
  170. 'prefix_length': 30,
  171. 'description': 'Test Prefix {}'.format(i + 1)
  172. }
  173. response = self.client.post(url, data, format='json', **self.header)
  174. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  175. self.assertEqual(response.data['prefix'], prefixes_to_be_created[i])
  176. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  177. self.assertEqual(response.data['description'], data['description'])
  178. # Try to create one more prefix
  179. response = self.client.post(url, {'prefix_length': 30}, format='json', **self.header)
  180. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  181. self.assertIn('detail', response.data)
  182. # Try to create invalid prefix type
  183. response = self.client.post(url, {'prefix_length': '30'}, format='json', **self.header)
  184. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  185. self.assertIn('prefix_length', response.data[0])
  186. def test_create_multiple_available_prefixes(self):
  187. """
  188. Test the creation of available prefixes within a parent prefix.
  189. """
  190. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), is_pool=True)
  191. url = reverse('ipam-api:prefix-available-prefixes', kwargs={'pk': prefix.pk})
  192. # Try to create five /30s (only four are available)
  193. data = [
  194. {'prefix_length': 30, 'description': 'Test Prefix 1'},
  195. {'prefix_length': 30, 'description': 'Test Prefix 2'},
  196. {'prefix_length': 30, 'description': 'Test Prefix 3'},
  197. {'prefix_length': 30, 'description': 'Test Prefix 4'},
  198. {'prefix_length': 30, 'description': 'Test Prefix 5'},
  199. ]
  200. response = self.client.post(url, data, format='json', **self.header)
  201. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  202. self.assertIn('detail', response.data)
  203. # Verify that no prefixes were created (the entire /28 is still available)
  204. response = self.client.get(url, **self.header)
  205. self.assertEqual(response.data[0]['prefix'], '192.0.2.0/28')
  206. # Create four /30s in a single request
  207. response = self.client.post(url, data[:4], format='json', **self.header)
  208. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  209. self.assertEqual(len(response.data), 4)
  210. def test_list_available_ips(self):
  211. """
  212. Test retrieval of all available IP addresses within a parent prefix.
  213. """
  214. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
  215. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  216. # Retrieve all available IPs
  217. response = self.client.get(url, **self.header)
  218. self.assertEqual(len(response.data), 8) # 8 because prefix.is_pool = True
  219. # Change the prefix to not be a pool and try again
  220. prefix.is_pool = False
  221. prefix.save()
  222. response = self.client.get(url, **self.header)
  223. self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.is_pool = False
  224. def test_create_single_available_ip(self):
  225. """
  226. Test retrieval of the first available IP address within a parent prefix.
  227. """
  228. vrf = VRF.objects.create(name='Test VRF 1', rd='1234')
  229. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/30'), vrf=vrf, is_pool=True)
  230. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  231. # Create all four available IPs with individual requests
  232. for i in range(1, 5):
  233. data = {
  234. 'description': 'Test IP {}'.format(i)
  235. }
  236. response = self.client.post(url, data, format='json', **self.header)
  237. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  238. self.assertEqual(response.data['vrf']['id'], vrf.pk)
  239. self.assertEqual(response.data['description'], data['description'])
  240. # Try to create one more IP
  241. response = self.client.post(url, {}, **self.header)
  242. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  243. self.assertIn('detail', response.data)
  244. def test_create_multiple_available_ips(self):
  245. """
  246. Test the creation of available IP addresses within a parent prefix.
  247. """
  248. prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/29'), is_pool=True)
  249. url = reverse('ipam-api:prefix-available-ips', kwargs={'pk': prefix.pk})
  250. # Try to create nine IPs (only eight are available)
  251. data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 10)] # 9 IPs
  252. response = self.client.post(url, data, format='json', **self.header)
  253. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  254. self.assertIn('detail', response.data)
  255. # Verify that no IPs were created (eight are still available)
  256. response = self.client.get(url, **self.header)
  257. self.assertEqual(len(response.data), 8)
  258. # Create all eight available IPs in a single request
  259. data = [{'description': 'Test IP {}'.format(i)} for i in range(1, 9)] # 8 IPs
  260. response = self.client.post(url, data, format='json', **self.header)
  261. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  262. self.assertEqual(len(response.data), 8)
  263. class IPAddressTest(APIViewTestCases.APIViewTestCase):
  264. model = IPAddress
  265. brief_fields = ['address', 'family', 'id', 'url']
  266. create_data = [
  267. {
  268. 'address': '192.168.0.4/24',
  269. },
  270. {
  271. 'address': '192.168.0.5/24',
  272. },
  273. {
  274. 'address': '192.168.0.6/24',
  275. },
  276. ]
  277. @classmethod
  278. def setUpTestData(cls):
  279. ip_addresses = (
  280. IPAddress(address=IPNetwork('192.168.0.1/24')),
  281. IPAddress(address=IPNetwork('192.168.0.2/24')),
  282. IPAddress(address=IPNetwork('192.168.0.3/24')),
  283. )
  284. IPAddress.objects.bulk_create(ip_addresses)
  285. class VLANGroupTest(APIViewTestCases.APIViewTestCase):
  286. model = VLANGroup
  287. brief_fields = ['id', 'name', 'slug', 'url', 'vlan_count']
  288. create_data = [
  289. {
  290. 'name': 'VLAN Group 4',
  291. 'slug': 'vlan-group-4',
  292. },
  293. {
  294. 'name': 'VLAN Group 5',
  295. 'slug': 'vlan-group-5',
  296. },
  297. {
  298. 'name': 'VLAN Group 6',
  299. 'slug': 'vlan-group-6',
  300. },
  301. ]
  302. @classmethod
  303. def setUpTestData(cls):
  304. vlan_groups = (
  305. VLANGroup(name='VLAN Group 1', slug='vlan-group-1'),
  306. VLANGroup(name='VLAN Group 2', slug='vlan-group-2'),
  307. VLANGroup(name='VLAN Group 3', slug='vlan-group-3'),
  308. )
  309. VLANGroup.objects.bulk_create(vlan_groups)
  310. class VLANTest(APIViewTestCases.APIViewTestCase):
  311. model = VLAN
  312. brief_fields = ['display_name', 'id', 'name', 'url', 'vid']
  313. @classmethod
  314. def setUpTestData(cls):
  315. vlan_groups = (
  316. VLANGroup(name='VLAN Group 1', slug='vlan-group-1'),
  317. VLANGroup(name='VLAN Group 2', slug='vlan-group-2'),
  318. )
  319. VLANGroup.objects.bulk_create(vlan_groups)
  320. vlans = (
  321. VLAN(name='VLAN 1', vid=1, group=vlan_groups[0]),
  322. VLAN(name='VLAN 2', vid=2, group=vlan_groups[0]),
  323. VLAN(name='VLAN 3', vid=3, group=vlan_groups[0]),
  324. )
  325. VLAN.objects.bulk_create(vlans)
  326. cls.create_data = [
  327. {
  328. 'vid': 4,
  329. 'name': 'VLAN 4',
  330. 'group': vlan_groups[1].pk,
  331. },
  332. {
  333. 'vid': 5,
  334. 'name': 'VLAN 5',
  335. 'group': vlan_groups[1].pk,
  336. },
  337. {
  338. 'vid': 6,
  339. 'name': 'VLAN 6',
  340. 'group': vlan_groups[1].pk,
  341. },
  342. ]
  343. def test_delete_vlan_with_prefix(self):
  344. """
  345. Attempt and fail to delete a VLAN with a Prefix assigned to it.
  346. """
  347. vlan = VLAN.objects.first()
  348. Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'), vlan=vlan)
  349. url = reverse('ipam-api:vlan-detail', kwargs={'pk': vlan.pk})
  350. with disable_warnings('django.request'):
  351. response = self.client.delete(url, **self.header)
  352. self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
  353. content = json.loads(response.content.decode('utf-8'))
  354. self.assertIn('detail', content)
  355. self.assertTrue(content['detail'].startswith('Unable to delete object.'))
  356. class ServiceTest(APIViewTestCases.APIViewTestCase):
  357. model = Service
  358. brief_fields = ['id', 'name', 'port', 'protocol', 'url']
  359. @classmethod
  360. def setUpTestData(cls):
  361. site = Site.objects.create(name='Site 1', slug='site-1')
  362. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  363. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
  364. devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
  365. devices = (
  366. Device(name='Device 1', site=site, device_type=devicetype, device_role=devicerole),
  367. Device(name='Device 2', site=site, device_type=devicetype, device_role=devicerole),
  368. )
  369. Device.objects.bulk_create(devices)
  370. services = (
  371. Service(device=devices[0], name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=1),
  372. Service(device=devices[0], name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=2),
  373. Service(device=devices[0], name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, port=3),
  374. )
  375. Service.objects.bulk_create(services)
  376. cls.create_data = [
  377. {
  378. 'device': devices[1].pk,
  379. 'name': 'Service 4',
  380. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  381. 'port': 4,
  382. },
  383. {
  384. 'device': devices[1].pk,
  385. 'name': 'Service 5',
  386. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  387. 'port': 5,
  388. },
  389. {
  390. 'device': devices[1].pk,
  391. 'name': 'Service 6',
  392. 'protocol': ServiceProtocolChoices.PROTOCOL_TCP,
  393. 'port': 6,
  394. },
  395. ]