test_api.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. import logging
  2. from django.test import tag
  3. from django.urls import reverse
  4. from netaddr import IPNetwork
  5. from rest_framework import status
  6. from core.models import ObjectType
  7. from dcim.choices import InterfaceModeChoices
  8. from dcim.models import Platform, Site
  9. from extras.choices import CustomFieldTypeChoices
  10. from extras.models import ConfigTemplate, CustomField
  11. from ipam.choices import VLANQinQRoleChoices
  12. from ipam.models import VLAN, VRF, Prefix
  13. from users.constants import TOKEN_PREFIX
  14. from users.models import Token
  15. from utilities.testing import (
  16. APITestCase,
  17. APIViewTestCases,
  18. create_test_device,
  19. create_test_nat_ip_pair,
  20. create_test_virtualmachine,
  21. disable_logging,
  22. )
  23. from virtualization.choices import *
  24. from virtualization.models import *
  25. class AppTestCase(APITestCase):
  26. def test_root(self):
  27. url = reverse('virtualization-api:api-root')
  28. response = self.client.get('{}?format=api'.format(url), **self.header)
  29. self.assertEqual(response.status_code, 200)
  30. class ClusterTypeTestCase(APIViewTestCases.APIViewTestCase):
  31. model = ClusterType
  32. brief_fields = ['cluster_count', 'description', 'display', 'id', 'name', 'slug', 'url']
  33. create_data = [
  34. {
  35. 'name': 'Cluster Type 4',
  36. 'slug': 'cluster-type-4',
  37. },
  38. {
  39. 'name': 'Cluster Type 5',
  40. 'slug': 'cluster-type-5',
  41. },
  42. {
  43. 'name': 'Cluster Type 6',
  44. 'slug': 'cluster-type-6',
  45. },
  46. ]
  47. bulk_update_data = {
  48. 'description': 'New description',
  49. }
  50. @classmethod
  51. def setUpTestData(cls):
  52. cluster_types = (
  53. ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
  54. ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
  55. ClusterType(name='Cluster Type 3', slug='cluster-type-3'),
  56. )
  57. ClusterType.objects.bulk_create(cluster_types)
  58. class ClusterGroupTestCase(APIViewTestCases.APIViewTestCase):
  59. model = ClusterGroup
  60. brief_fields = ['cluster_count', 'description', 'display', 'id', 'name', 'slug', 'url']
  61. create_data = [
  62. {
  63. 'name': 'Cluster Group 4',
  64. 'slug': 'cluster-type-4',
  65. },
  66. {
  67. 'name': 'Cluster Group 5',
  68. 'slug': 'cluster-type-5',
  69. },
  70. {
  71. 'name': 'Cluster Group 6',
  72. 'slug': 'cluster-type-6',
  73. },
  74. ]
  75. bulk_update_data = {
  76. 'description': 'New description',
  77. }
  78. @classmethod
  79. def setUpTestData(cls):
  80. cluster_Groups = (
  81. ClusterGroup(name='Cluster Group 1', slug='cluster-type-1'),
  82. ClusterGroup(name='Cluster Group 2', slug='cluster-type-2'),
  83. ClusterGroup(name='Cluster Group 3', slug='cluster-type-3'),
  84. )
  85. ClusterGroup.objects.bulk_create(cluster_Groups)
  86. class ClusterTestCase(APIViewTestCases.APIViewTestCase):
  87. model = Cluster
  88. brief_fields = ['description', 'display', 'id', 'name', 'url', 'virtualmachine_count']
  89. bulk_update_data = {
  90. 'status': 'offline',
  91. 'comments': 'New comment',
  92. }
  93. @classmethod
  94. def setUpTestData(cls):
  95. cluster_types = (
  96. ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
  97. ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
  98. )
  99. ClusterType.objects.bulk_create(cluster_types)
  100. cluster_groups = (
  101. ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
  102. ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
  103. )
  104. ClusterGroup.objects.bulk_create(cluster_groups)
  105. clusters = (
  106. Cluster(
  107. name='Cluster 1',
  108. type=cluster_types[0],
  109. group=cluster_groups[0],
  110. status=ClusterStatusChoices.STATUS_PLANNED,
  111. ),
  112. Cluster(
  113. name='Cluster 2',
  114. type=cluster_types[0],
  115. group=cluster_groups[0],
  116. status=ClusterStatusChoices.STATUS_PLANNED,
  117. ),
  118. Cluster(
  119. name='Cluster 3',
  120. type=cluster_types[0],
  121. group=cluster_groups[0],
  122. status=ClusterStatusChoices.STATUS_PLANNED,
  123. ),
  124. )
  125. for cluster in clusters:
  126. cluster.save()
  127. cls.create_data = [
  128. {
  129. 'name': 'Cluster 4',
  130. 'type': cluster_types[1].pk,
  131. 'group': cluster_groups[1].pk,
  132. 'status': ClusterStatusChoices.STATUS_STAGING,
  133. },
  134. {
  135. 'name': 'Cluster 5',
  136. 'type': cluster_types[1].pk,
  137. 'group': cluster_groups[1].pk,
  138. 'status': ClusterStatusChoices.STATUS_STAGING,
  139. },
  140. {
  141. 'name': 'Cluster 6',
  142. 'type': cluster_types[1].pk,
  143. 'group': cluster_groups[1].pk,
  144. 'status': ClusterStatusChoices.STATUS_STAGING,
  145. },
  146. ]
  147. class VirtualMachineTypeTestCase(APIViewTestCases.APIViewTestCase):
  148. model = VirtualMachineType
  149. brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url']
  150. user_permissions = ('dcim.view_platform', 'virtualization.view_virtualmachine')
  151. @classmethod
  152. def setUpTestData(cls):
  153. cls.platforms = (
  154. Platform.objects.create(name='Platform 1', slug='platform-1'),
  155. Platform.objects.create(name='Platform 2', slug='platform-2'),
  156. Platform.objects.create(name='Platform 3', slug='platform-3'),
  157. )
  158. cls.virtual_machine_types = (
  159. VirtualMachineType.objects.create(
  160. name='Virtual Machine Type 1',
  161. slug='virtual-machine-type-1',
  162. default_platform=cls.platforms[0],
  163. default_vcpus=1,
  164. default_memory=1024,
  165. ),
  166. VirtualMachineType.objects.create(
  167. name='Virtual Machine Type 2',
  168. slug='virtual-machine-type-2',
  169. default_platform=cls.platforms[1],
  170. default_vcpus=2,
  171. default_memory=2048,
  172. ),
  173. VirtualMachineType.objects.create(
  174. name='Virtual Machine Type 3',
  175. slug='virtual-machine-type-3',
  176. default_platform=cls.platforms[2],
  177. default_vcpus=4,
  178. default_memory=4096,
  179. ),
  180. )
  181. cls.create_data = [
  182. {
  183. 'name': 'Virtual Machine Type 4',
  184. 'slug': 'virtual-machine-type-4',
  185. 'default_platform': cls.platforms[0].pk,
  186. 'default_vcpus': 1,
  187. 'default_memory': 1024,
  188. },
  189. {
  190. 'name': 'Virtual Machine Type 5',
  191. 'slug': 'virtual-machine-type-5',
  192. 'default_platform': cls.platforms[1].pk,
  193. 'default_vcpus': 2,
  194. 'default_memory': 2048,
  195. },
  196. {
  197. 'name': 'Virtual Machine Type 6',
  198. 'slug': 'virtual-machine-type-6',
  199. 'default_platform': cls.platforms[2].pk,
  200. 'default_vcpus': 4,
  201. 'default_memory': 4096,
  202. },
  203. ]
  204. cls.bulk_update_data = {
  205. 'default_platform': cls.platforms[2].pk,
  206. 'default_vcpus': 8,
  207. 'default_memory': 8192,
  208. 'description': 'New description',
  209. }
  210. class VirtualMachineTestCase(APIViewTestCases.APIViewTestCase):
  211. model = VirtualMachine
  212. brief_fields = ['description', 'display', 'id', 'name', 'url']
  213. bulk_update_data = {
  214. 'status': 'staged',
  215. }
  216. user_permissions = ('dcim.view_platform', 'virtualization.view_virtualmachinetype')
  217. @classmethod
  218. def setUpTestData(cls):
  219. clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
  220. clustergroup = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1')
  221. cls.sites = (
  222. Site(name='Site 1', slug='site-1'),
  223. Site(name='Site 2', slug='site-2'),
  224. Site(name='Site 3', slug='site-3'),
  225. )
  226. Site.objects.bulk_create(cls.sites)
  227. cls.clusters = (
  228. Cluster(name='Cluster 1', type=clustertype, scope=cls.sites[0], group=clustergroup),
  229. Cluster(name='Cluster 2', type=clustertype, scope=cls.sites[1], group=clustergroup),
  230. Cluster(name='Cluster 3', type=clustertype),
  231. )
  232. for cluster in cls.clusters:
  233. cluster.save()
  234. cls.devices = (
  235. create_test_device('device1', site=cls.sites[0], cluster=cls.clusters[0]),
  236. create_test_device('device2', site=cls.sites[1], cluster=cls.clusters[1]),
  237. )
  238. cls.platforms = (
  239. Platform.objects.create(name='Platform 1', slug='platform-1'),
  240. Platform.objects.create(name='Platform 2', slug='platform-2'),
  241. Platform.objects.create(name='Platform 3', slug='platform-3'),
  242. )
  243. cls.vm_types = (
  244. VirtualMachineType.objects.create(
  245. name='Virtual Machine Type 1',
  246. slug='virtual-machine-type-1',
  247. default_platform=cls.platforms[0],
  248. default_vcpus=2,
  249. default_memory=4096,
  250. ),
  251. VirtualMachineType.objects.create(
  252. name='Virtual Machine Type 2',
  253. slug='virtual-machine-type-2',
  254. default_platform=cls.platforms[1],
  255. default_vcpus=4,
  256. default_memory=8192,
  257. ),
  258. )
  259. virtual_machines = (
  260. VirtualMachine(
  261. name='Virtual Machine 1',
  262. virtual_machine_type=cls.vm_types[0],
  263. site=cls.sites[0],
  264. cluster=cls.clusters[0],
  265. device=cls.devices[0],
  266. platform=cls.platforms[0],
  267. vcpus=2,
  268. memory=4096,
  269. local_context_data={'A': 1},
  270. ),
  271. VirtualMachine(
  272. name='Virtual Machine 2',
  273. site=cls.sites[0],
  274. cluster=cls.clusters[0],
  275. local_context_data={'B': 2},
  276. ),
  277. VirtualMachine(
  278. name='Virtual Machine 3',
  279. site=cls.sites[0],
  280. cluster=cls.clusters[0],
  281. local_context_data={'C': 3},
  282. start_on_boot=VirtualMachineStartOnBootChoices.STATUS_ON,
  283. ),
  284. )
  285. VirtualMachine.objects.bulk_create(virtual_machines)
  286. cls.create_data = [
  287. {
  288. 'name': 'Virtual Machine 4',
  289. 'site': cls.sites[1].pk,
  290. 'cluster': cls.clusters[1].pk,
  291. 'device': cls.devices[1].pk,
  292. 'virtual_machine_type': cls.vm_types[0].pk,
  293. },
  294. {
  295. 'name': 'Virtual Machine 5',
  296. 'site': cls.sites[1].pk,
  297. 'cluster': cls.clusters[1].pk,
  298. 'virtual_machine_type': cls.vm_types[1].pk,
  299. },
  300. {
  301. 'name': 'Virtual Machine 6',
  302. 'site': cls.sites[1].pk,
  303. },
  304. {
  305. 'name': 'Virtual Machine 7',
  306. 'cluster': cls.clusters[2].pk,
  307. 'virtual_machine_type': cls.vm_types[0].pk,
  308. 'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_ON,
  309. },
  310. ]
  311. def test_virtual_machine_type_defaults_applied_on_create(self):
  312. data = {
  313. 'name': 'Virtual Machine With Defaults',
  314. 'site': self.sites[1].pk,
  315. 'cluster': self.clusters[1].pk,
  316. 'virtual_machine_type': self.vm_types[0].pk,
  317. 'platform': None,
  318. 'vcpus': None,
  319. 'memory': None,
  320. }
  321. self.add_permissions('virtualization.add_virtualmachine')
  322. response = self.client.post(self._get_list_url(), data, format='json', **self.header)
  323. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  324. vm = VirtualMachine.objects.get(pk=response.data['id'])
  325. self.assertEqual(vm.virtual_machine_type, self.vm_types[0])
  326. self.assertEqual(vm.platform, self.vm_types[0].default_platform)
  327. self.assertEqual(vm.vcpus, self.vm_types[0].default_vcpus)
  328. self.assertEqual(vm.memory, self.vm_types[0].default_memory)
  329. def test_virtual_machine_type_defaults_do_not_override_explicit_values(self):
  330. data = {
  331. 'name': 'Virtual Machine With Explicit Values',
  332. 'site': self.sites[1].pk,
  333. 'cluster': self.clusters[1].pk,
  334. 'virtual_machine_type': self.vm_types[0].pk,
  335. 'platform': self.platforms[2].pk,
  336. 'vcpus': 6,
  337. 'memory': 12288,
  338. }
  339. self.add_permissions('virtualization.add_virtualmachine')
  340. response = self.client.post(self._get_list_url(), data, format='json', **self.header)
  341. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  342. vm = VirtualMachine.objects.get(pk=response.data['id'])
  343. self.assertEqual(vm.virtual_machine_type, self.vm_types[0])
  344. self.assertEqual(vm.platform, self.platforms[2])
  345. self.assertEqual(vm.vcpus, 6)
  346. self.assertEqual(vm.memory, 12288)
  347. def test_setting_virtual_machine_type_on_existing_vm_does_not_backfill_defaults(self):
  348. vm = VirtualMachine.objects.get(name='Virtual Machine 2')
  349. self.add_permissions('virtualization.change_virtualmachine')
  350. response = self.client.patch(
  351. self._get_detail_url(vm),
  352. {'virtual_machine_type': self.vm_types[1].pk},
  353. format='json',
  354. **self.header,
  355. )
  356. self.assertHttpStatus(response, status.HTTP_200_OK)
  357. vm.refresh_from_db()
  358. self.assertEqual(vm.virtual_machine_type, self.vm_types[1])
  359. self.assertIsNone(vm.platform)
  360. self.assertIsNone(vm.vcpus)
  361. self.assertIsNone(vm.memory)
  362. def test_config_context_included_by_default_in_list_view(self):
  363. """
  364. Check that config context data is included by default in the virtual machines list.
  365. """
  366. virtualmachine = VirtualMachine.objects.first()
  367. url = '{}?id={}'.format(reverse('virtualization-api:virtualmachine-list'), virtualmachine.pk)
  368. self.add_permissions('virtualization.view_virtualmachine')
  369. response = self.client.get(url, **self.header)
  370. self.assertEqual(response.data['results'][0].get('config_context', {}).get('A'), 1)
  371. def test_unique_name_per_cluster_constraint(self):
  372. """
  373. Check that creating a virtual machine with a duplicate name fails.
  374. """
  375. data = {
  376. 'name': 'Virtual Machine 1',
  377. 'cluster': Cluster.objects.first().pk,
  378. }
  379. url = reverse('virtualization-api:virtualmachine-list')
  380. self.add_permissions('virtualization.add_virtualmachine')
  381. response = self.client.post(url, data, format='json', **self.header)
  382. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  383. def test_render_config(self):
  384. configtemplate = ConfigTemplate.objects.create(
  385. name='Config Template 1',
  386. template_code='Config for virtual machine {{ virtualmachine.name }}'
  387. )
  388. vm = VirtualMachine.objects.first()
  389. vm.config_template = configtemplate
  390. vm.save()
  391. self.add_permissions(
  392. 'virtualization.render_config_virtualmachine', 'virtualization.view_virtualmachine'
  393. )
  394. url = reverse('virtualization-api:virtualmachine-render-config', kwargs={'pk': vm.pk})
  395. response = self.client.post(url, {}, format='json', **self.header)
  396. self.assertHttpStatus(response, status.HTTP_200_OK)
  397. self.assertEqual(response.data['content'], f'Config for virtual machine {vm.name}')
  398. def test_render_config_without_permission(self):
  399. configtemplate = ConfigTemplate.objects.create(
  400. name='Config Template 1',
  401. template_code='Config for virtual machine {{ virtualmachine.name }}'
  402. )
  403. vm = VirtualMachine.objects.first()
  404. vm.config_template = configtemplate
  405. vm.save()
  406. # No permissions added - user has no render_config permission
  407. url = reverse('virtualization-api:virtualmachine-render-config', kwargs={'pk': vm.pk})
  408. response = self.client.post(url, {}, format='json', **self.header)
  409. self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
  410. def test_render_config_token_write_enabled(self):
  411. configtemplate = ConfigTemplate.objects.create(
  412. name='Config Template 1',
  413. template_code='Config for virtual machine {{ virtualmachine.name }}'
  414. )
  415. vm = VirtualMachine.objects.first()
  416. vm.config_template = configtemplate
  417. vm.save()
  418. self.add_permissions('virtualization.render_config_virtualmachine', 'virtualization.view_virtualmachine')
  419. url = reverse('virtualization-api:virtualmachine-render-config', kwargs={'pk': vm.pk})
  420. # Request without token auth should fail with PermissionDenied
  421. response = self.client.post(url, {}, format='json')
  422. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  423. # Create token with write_enabled=False
  424. token = Token.objects.create(version=2, user=self.user, write_enabled=False)
  425. token_header = f'Bearer {TOKEN_PREFIX}{token.key}.{token.token}'
  426. # Request with write-disabled token should fail
  427. response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
  428. self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
  429. # Enable write and retry
  430. token.write_enabled = True
  431. token.save()
  432. response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
  433. self.assertHttpStatus(response, status.HTTP_200_OK)
  434. def test_list_object_includes_nat_inside_on_primary_ip(self):
  435. virtualmachine = create_test_virtualmachine('natted-vm')
  436. interface = VMInterface.objects.create(virtual_machine=virtualmachine, name='eth0')
  437. real_ip, nat_ip = create_test_nat_ip_pair(
  438. real_address='10.0.1.10/32',
  439. nat_address='198.51.100.20/32',
  440. inside_interface=interface,
  441. )
  442. virtualmachine.primary_ip4 = nat_ip
  443. virtualmachine.save()
  444. self.add_permissions('virtualization.view_virtualmachine', 'ipam.view_ipaddress')
  445. response = self.client.get(f'{self._get_list_url()}?id={virtualmachine.pk}', **self.header)
  446. self.assertHttpStatus(response, status.HTTP_200_OK)
  447. result = response.data['results'][0]
  448. for field in ('primary_ip', 'primary_ip4'):
  449. self.assertEqual(result[field]['address'], str(nat_ip.address))
  450. self.assertEqual(result[field]['nat_inside']['address'], str(real_ip.address))
  451. self.assertEqual(result[field]['nat_outside'], [])
  452. def test_get_object_includes_nat_outside_on_primary_ip(self):
  453. virtualmachine = create_test_virtualmachine('real-ip-vm')
  454. interface = VMInterface.objects.create(virtual_machine=virtualmachine, name='eth0')
  455. real_ip, nat_ip = create_test_nat_ip_pair(
  456. real_address='10.0.1.11/32',
  457. nat_address='198.51.100.21/32',
  458. inside_interface=interface,
  459. )
  460. virtualmachine.primary_ip4 = real_ip
  461. virtualmachine.save()
  462. self.add_permissions('virtualization.view_virtualmachine', 'ipam.view_ipaddress')
  463. response = self.client.get(
  464. self._get_detail_url(virtualmachine),
  465. **self.header,
  466. )
  467. self.assertHttpStatus(response, status.HTTP_200_OK)
  468. for field in ('primary_ip', 'primary_ip4'):
  469. self.assertEqual(response.data[field]['address'], str(real_ip.address))
  470. self.assertIsNone(response.data[field]['nat_inside'])
  471. self.assertCountEqual(
  472. [ip['address'] for ip in response.data[field]['nat_outside']],
  473. [str(nat_ip.address)],
  474. )
  475. def test_render_config_with_config_template_id(self):
  476. default_template = ConfigTemplate.objects.create(
  477. name='Default Template',
  478. template_code='Default config for {{ virtualmachine.name }}'
  479. )
  480. override_template = ConfigTemplate.objects.create(
  481. name='Override Template',
  482. template_code='Override config for {{ virtualmachine.name }}'
  483. )
  484. vm = VirtualMachine.objects.first()
  485. vm.config_template = default_template
  486. vm.save()
  487. self.add_permissions(
  488. 'virtualization.render_config_virtualmachine', 'virtualization.view_virtualmachine',
  489. 'extras.view_configtemplate'
  490. )
  491. url = reverse('virtualization-api:virtualmachine-render-config', kwargs={'pk': vm.pk})
  492. # Render with override template
  493. response = self.client.post(url, {'config_template_id': override_template.pk}, format='json', **self.header)
  494. self.assertHttpStatus(response, status.HTTP_200_OK)
  495. self.assertEqual(response.data['content'], f'Override config for {vm.name}')
  496. # Render with nonexistent config_template_id
  497. response = self.client.post(url, {'config_template_id': 999999}, format='json', **self.header)
  498. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  499. # Render with non-integer config_template_id
  500. response = self.client.post(url, {'config_template_id': 'abc'}, format='json', **self.header)
  501. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  502. # Without view_configtemplate permission, override template should not be accessible
  503. self.remove_permissions('extras.view_configtemplate')
  504. response = self.client.post(url, {'config_template_id': override_template.pk}, format='json', **self.header)
  505. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  506. class VMInterfaceTestCase(APIViewTestCases.APIViewTestCase):
  507. model = VMInterface
  508. brief_fields = ['description', 'display', 'id', 'name', 'url', 'virtual_machine']
  509. bulk_update_data = {
  510. 'description': 'New description',
  511. }
  512. graphql_base_name = 'vm_interface'
  513. user_permissions = ('virtualization.view_virtualmachine', )
  514. @classmethod
  515. def setUpTestData(cls):
  516. virtualmachine = create_test_virtualmachine('Virtual Machine 1')
  517. interfaces = (
  518. VMInterface(virtual_machine=virtualmachine, name='Interface 1'),
  519. VMInterface(virtual_machine=virtualmachine, name='Interface 2'),
  520. VMInterface(virtual_machine=virtualmachine, name='Interface 3'),
  521. )
  522. VMInterface.objects.bulk_create(interfaces)
  523. vlans = (
  524. VLAN(name='VLAN 1', vid=1),
  525. VLAN(name='VLAN 2', vid=2),
  526. VLAN(name='VLAN 3', vid=3),
  527. VLAN(name='SVLAN 1', vid=1001, qinq_role=VLANQinQRoleChoices.ROLE_SERVICE),
  528. )
  529. VLAN.objects.bulk_create(vlans)
  530. vrfs = (
  531. VRF(name='VRF 1'),
  532. VRF(name='VRF 2'),
  533. VRF(name='VRF 3'),
  534. )
  535. VRF.objects.bulk_create(vrfs)
  536. cls.create_data = [
  537. {
  538. 'virtual_machine': virtualmachine.pk,
  539. 'name': 'Interface 4',
  540. 'mode': InterfaceModeChoices.MODE_TAGGED,
  541. 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
  542. 'untagged_vlan': vlans[2].pk,
  543. 'vrf': vrfs[0].pk,
  544. },
  545. {
  546. 'virtual_machine': virtualmachine.pk,
  547. 'name': 'Interface 5',
  548. 'mode': InterfaceModeChoices.MODE_TAGGED,
  549. 'bridge': interfaces[0].pk,
  550. 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
  551. 'untagged_vlan': vlans[2].pk,
  552. 'vrf': vrfs[1].pk,
  553. },
  554. {
  555. 'virtual_machine': virtualmachine.pk,
  556. 'name': 'Interface 6',
  557. 'mode': InterfaceModeChoices.MODE_TAGGED,
  558. 'parent': interfaces[1].pk,
  559. 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
  560. 'untagged_vlan': vlans[2].pk,
  561. 'vrf': vrfs[2].pk,
  562. },
  563. {
  564. 'virtual_machine': virtualmachine.pk,
  565. 'name': 'Interface 7',
  566. 'mode': InterfaceModeChoices.MODE_Q_IN_Q,
  567. 'qinq_svlan': vlans[3].pk,
  568. },
  569. ]
  570. @tag('regression')
  571. def test_set_vminterface_as_object_in_custom_field(self):
  572. cf = CustomField.objects.create(
  573. name='associated_interface',
  574. type=CustomFieldTypeChoices.TYPE_OBJECT,
  575. related_object_type=ObjectType.objects.get_for_model(VMInterface),
  576. required=False
  577. )
  578. cf.object_types.set([ObjectType.objects.get_for_model(Prefix)])
  579. cf.save()
  580. prefix = Prefix.objects.create(prefix=IPNetwork('10.0.0.0/12'))
  581. vmi = VMInterface.objects.first()
  582. url = reverse('ipam-api:prefix-detail', kwargs={'pk': prefix.pk})
  583. data = {
  584. 'custom_fields': {
  585. 'associated_interface': vmi.id,
  586. },
  587. }
  588. self.add_permissions('ipam.change_prefix')
  589. response = self.client.patch(url, data, format='json', **self.header)
  590. self.assertEqual(response.status_code, 200)
  591. prefix_data = response.json()
  592. self.assertEqual(prefix_data['custom_fields']['associated_interface']['id'], vmi.id)
  593. reloaded_prefix = Prefix.objects.get(pk=prefix.pk)
  594. self.assertEqual(prefix.pk, reloaded_prefix.pk)
  595. self.assertNotEqual(reloaded_prefix.cf['associated_interface'], None)
  596. def test_bulk_delete_child_interfaces(self):
  597. interface1 = VMInterface.objects.get(name='Interface 1')
  598. virtual_machine = interface1.virtual_machine
  599. self.add_permissions('virtualization.delete_vminterface')
  600. # Create a child interface
  601. child = VMInterface.objects.create(
  602. virtual_machine=virtual_machine,
  603. name='Interface 1A',
  604. parent=interface1
  605. )
  606. self.assertEqual(virtual_machine.interfaces.count(), 4)
  607. # Attempt to delete only the parent interface
  608. url = self._get_detail_url(interface1)
  609. with disable_logging(level=logging.WARNING):
  610. self.client.delete(url, **self.header)
  611. self.assertEqual(virtual_machine.interfaces.count(), 4) # Parent was not deleted
  612. # Attempt to bulk delete parent & child together
  613. data = [
  614. {"id": interface1.pk},
  615. {"id": child.pk},
  616. ]
  617. self.client.delete(self._get_list_url(), data, format='json', **self.header)
  618. self.assertEqual(virtual_machine.interfaces.count(), 2) # Child & parent were both deleted
  619. class VirtualDiskTestCase(APIViewTestCases.APIViewTestCase):
  620. model = VirtualDisk
  621. brief_fields = ['description', 'display', 'id', 'name', 'size', 'url', 'virtual_machine']
  622. bulk_update_data = {
  623. 'size': 888,
  624. }
  625. graphql_base_name = 'virtual_disk'
  626. user_permissions = ('virtualization.view_virtualmachine', )
  627. @classmethod
  628. def setUpTestData(cls):
  629. virtualmachine = create_test_virtualmachine('Virtual Machine 1')
  630. disks = (
  631. VirtualDisk(virtual_machine=virtualmachine, name='Disk 1', size=10),
  632. VirtualDisk(virtual_machine=virtualmachine, name='Disk 2', size=20),
  633. VirtualDisk(virtual_machine=virtualmachine, name='Disk 3', size=30),
  634. )
  635. VirtualDisk.objects.bulk_create(disks)
  636. cls.create_data = [
  637. {
  638. 'virtual_machine': virtualmachine.pk,
  639. 'name': 'Disk 4',
  640. 'size': 10,
  641. },
  642. {
  643. 'virtual_machine': virtualmachine.pk,
  644. 'name': 'Disk 5',
  645. 'size': 20,
  646. },
  647. {
  648. 'virtual_machine': virtualmachine.pk,
  649. 'name': 'Disk 6',
  650. 'size': 30,
  651. },
  652. ]