Kubernetes integration with OpenStack networking
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_namespace_security_groups.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. # Copyright (c) 2018 Red Hat, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import mock
  16. from kuryr_kubernetes import constants
  17. from kuryr_kubernetes.controller.drivers import namespace_security_groups
  18. from kuryr_kubernetes.tests import base as test_base
  19. from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix
  20. from neutronclient.common import exceptions as n_exc
  21. def get_pod_obj():
  22. return {
  23. 'status': {
  24. 'qosClass': 'BestEffort',
  25. 'hostIP': '192.168.1.2',
  26. },
  27. 'kind': 'Pod',
  28. 'spec': {
  29. 'schedulerName': 'default-scheduler',
  30. 'containers': [{
  31. 'name': 'busybox',
  32. 'image': 'busybox',
  33. 'resources': {}
  34. }],
  35. 'nodeName': 'kuryr-devstack'
  36. },
  37. 'metadata': {
  38. 'name': 'busybox-sleep1',
  39. 'namespace': 'default',
  40. 'resourceVersion': '53808',
  41. 'selfLink': '/api/v1/namespaces/default/pods/busybox-sleep1',
  42. 'uid': '452176db-4a85-11e7-80bd-fa163e29dbbb',
  43. 'annotations': {
  44. 'openstack.org/kuryr-vif': {}
  45. }
  46. }}
  47. def get_namespace_obj():
  48. return {
  49. 'metadata': {
  50. 'annotations': {
  51. constants.K8S_ANNOTATION_NET_CRD: 'net_crd_url_sample'
  52. }
  53. }
  54. }
  55. def get_no_match_crd_namespace_obj():
  56. return {
  57. "kind": "Namespace",
  58. "metadata": {
  59. "annotations": {
  60. "openstack.org/kuryr-namespace-label": '{"name": "dev"}',
  61. "openstack.org/kuryr-net-crd": "ns-dev"
  62. },
  63. "labels": {"name": "prod"},
  64. "name": "prod",
  65. "selfLink": "/api/v1/namespaces/dev"}}
  66. def get_match_crd_namespace_obj():
  67. return {
  68. "kind": "Namespace",
  69. "metadata": {
  70. "annotations": {
  71. "openstack.org/kuryr-namespace-label": '{"name": "dev"}',
  72. "openstack.org/kuryr-net-crd": "ns-dev"
  73. },
  74. "labels": {
  75. "name": "dev"
  76. },
  77. "name": "dev",
  78. "selfLink": "/api/v1/namespaces/dev"}}
  79. def get_match_crd_pod_obj():
  80. return {
  81. 'kind': 'Pod',
  82. 'metadata': {
  83. 'name': mock.sentinel.pod_name,
  84. 'namespace': 'dev',
  85. 'labels': {
  86. 'tier': 'backend'},
  87. 'annotations': {
  88. 'openstack.org/kuryr-pod-label': '{"tier": "backend"}'}},
  89. 'status': {'podIP': mock.sentinel.podIP}}
  90. def get_sg_rule():
  91. pod_ip = get_match_crd_pod_obj()['status'].get('podIP')
  92. return {
  93. "namespace": 'dev',
  94. "security_group_rule": {
  95. "description": "Kuryr-Kubernetes NetPolicy SG rule",
  96. "direction": "ingress",
  97. "ethertype": "IPv4",
  98. "id": 'f15ff50a-e8a4-4872-81bf-a04cbb8cb388',
  99. "port_range_max": 6379,
  100. "port_range_min": 6379,
  101. "protocol": "tcp",
  102. "remote_ip_prefix": pod_ip,
  103. "security_group_id": '36923e76-026c-422b-8dfd-7292e7c88228'}}
  104. def get_matched_crd_obj():
  105. return {
  106. "kind": "KuryrNetPolicy",
  107. "metadata": {"name": "np-test-network-policy",
  108. "namespace": "default"},
  109. "spec": {
  110. "egressSgRules": [],
  111. "ingressSgRules": [get_sg_rule()],
  112. "networkpolicy_spec": {
  113. "ingress": [
  114. {"from": [
  115. {"namespaceSelector": {
  116. "matchLabels": {"name": "dev"}}}],
  117. "ports": [
  118. {"port": 6379,
  119. "protocol": "TCP"}]}],
  120. "podSelector": {"matchLabels": {"app": "demo"}},
  121. "policyTypes": ["Ingress"]},
  122. "podSelector": {"matchLabels": {"app": "demo"}},
  123. "securityGroupId": '36923e76-026c-422b-8dfd-7292e7c88228'}}
  124. def get_crd_obj_no_match():
  125. return {
  126. "kind": "KuryrNetPolicy",
  127. "metadata": {"name": "np-test-network-policy",
  128. "namespace": "default"},
  129. "spec": {
  130. "egressSgRules": [],
  131. "ingressSgRules": [],
  132. "networkpolicy_spec": {
  133. "ingress": [
  134. {"from": [
  135. {"namespaceSelector": {
  136. "matchLabels": {"name": "dev"}}}],
  137. "ports": [
  138. {"port": 6379,
  139. "protocol": "TCP"}]}],
  140. "podSelector": {"matchLabels": {"app": "demo"}},
  141. "policyTypes": ["Ingress"]},
  142. "podSelector": {"matchLabels": {"app": "demo"}},
  143. "securityGroupId": '36923e76-026c-422b-8dfd-7292e7c88228'}}
  144. def get_crd_obj_with_all_selectors():
  145. return {
  146. "kind": "KuryrNetPolicy",
  147. "metadata": {"name": "np-test-network-policy",
  148. "namespace": "default"},
  149. "spec": {
  150. "egressSgRules": [],
  151. "ingressSgRules": [],
  152. "networkpolicy_spec": {
  153. "ingress": [
  154. {"from": [
  155. {"namespaceSelector": {
  156. "matchLabels": {"name": "dev"}},
  157. "podSelector": {
  158. "matchLabels": {"tier": "backend"}}}],
  159. "ports": [
  160. {"port": 6379,
  161. "protocol": "TCP"}]}],
  162. "podSelector": {"matchLabels": {"app": "demo"}},
  163. "policyTypes": ["Ingress"]},
  164. "podSelector": {"matchLabels": {"app": "demo"}},
  165. "securityGroupId": '36923e76-026c-422b-8dfd-7292e7c88228'}}
  166. class TestNamespacePodSecurityGroupsDriver(test_base.TestCase):
  167. @mock.patch('kuryr_kubernetes.controller.drivers.'
  168. 'namespace_security_groups._get_net_crd')
  169. @mock.patch('kuryr_kubernetes.config.CONF')
  170. def test_get_security_groups(self, m_cfg, m_get_crd):
  171. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  172. m_driver = mock.MagicMock(spec=cls)
  173. pod = get_pod_obj()
  174. project_id = mock.sentinel.project_id
  175. sg_list = [mock.sentinel.sg_id]
  176. m_cfg.neutron_defaults.pod_security_groups = sg_list
  177. sg_id = mock.sentinel.sg_id
  178. extra_sg = mock.sentinel.extra_sg
  179. net_crd = {
  180. 'spec': {
  181. 'sgId': sg_id
  182. }
  183. }
  184. m_get_crd.return_value = net_crd
  185. m_driver._get_extra_sg.return_value = [extra_sg]
  186. ret = cls.get_security_groups(m_driver, pod, project_id)
  187. expected_sg = [str(sg_id), str(extra_sg), sg_list[0]]
  188. self.assertEqual(ret, expected_sg)
  189. m_get_crd.assert_called_once_with(pod['metadata']['namespace'])
  190. def test_create_namespace_sg(self):
  191. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  192. m_driver = mock.MagicMock(spec=cls)
  193. namespace = 'test'
  194. project_id = mock.sentinel.project_id
  195. sg = {'id': mock.sentinel.sg}
  196. subnet_cidr = mock.sentinel.subnet_cidr
  197. crd_spec = {
  198. 'subnetCIDR': subnet_cidr
  199. }
  200. neutron = self.useFixture(k_fix.MockNeutronClient()).client
  201. neutron.create_security_group.return_value = {'security_group': sg}
  202. create_sg_resp = cls.create_namespace_sg(m_driver, namespace,
  203. project_id, crd_spec)
  204. self.assertEqual(create_sg_resp, {'sgId': sg['id']})
  205. neutron.create_security_group.assert_called_once()
  206. neutron.create_security_group_rule.assert_called_once()
  207. def test_create_namespace_sg_exception(self):
  208. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  209. m_driver = mock.MagicMock(spec=cls)
  210. namespace = 'test'
  211. project_id = mock.sentinel.project_id
  212. subnet_cidr = mock.sentinel.subnet_cidr
  213. crd_spec = {
  214. 'subnetCIDR': subnet_cidr
  215. }
  216. neutron = self.useFixture(k_fix.MockNeutronClient()).client
  217. neutron.create_security_group.side_effect = (
  218. n_exc.NeutronClientException)
  219. self.assertRaises(n_exc.NeutronClientException,
  220. cls.create_namespace_sg, m_driver,
  221. namespace, project_id, crd_spec)
  222. neutron.create_security_group.assert_called_once()
  223. neutron.create_security_group_rule.assert_not_called()
  224. def test_delete_sg(self):
  225. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  226. m_driver = mock.MagicMock(spec=cls)
  227. neutron = self.useFixture(k_fix.MockNeutronClient()).client
  228. sg_id = mock.sentinel.sg_id
  229. cls.delete_sg(m_driver, sg_id)
  230. neutron.delete_security_group.assert_called_once_with(sg_id)
  231. def test_delete_sg_exception(self):
  232. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  233. m_driver = mock.MagicMock(spec=cls)
  234. neutron = self.useFixture(k_fix.MockNeutronClient()).client
  235. sg_id = mock.sentinel.sg_id
  236. neutron.delete_security_group.side_effect = (
  237. n_exc.NeutronClientException)
  238. self.assertRaises(n_exc.NeutronClientException, cls.delete_sg,
  239. m_driver, sg_id)
  240. neutron.delete_security_group.assert_called_once_with(sg_id)
  241. def test_delete_sg_not_found(self):
  242. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  243. m_driver = mock.MagicMock(spec=cls)
  244. neutron = self.useFixture(k_fix.MockNeutronClient()).client
  245. sg_id = mock.sentinel.sg_id
  246. neutron.delete_security_group.side_effect = n_exc.NotFound
  247. cls.delete_sg(m_driver, sg_id)
  248. neutron.delete_security_group.assert_called_once_with(sg_id)
  249. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  250. 'patch_kuryr_crd')
  251. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  252. 'delete_security_group_rule')
  253. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  254. 'get_kuryrnetpolicy_crds')
  255. def test_delete_namespace_sg_rule(self, m_get_knp_crd, m_delete_sg_rule,
  256. m_patch_kuryr_crd):
  257. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  258. m_driver = mock.MagicMock(spec=cls)
  259. i_rule = get_matched_crd_obj()['spec']['ingressSgRules'][0]
  260. sg_rule_id = i_rule.get('security_group_rule')['id']
  261. m_get_knp_crd.return_value = {"items": [get_matched_crd_obj()]}
  262. cls.delete_namespace_sg_rules(m_driver, get_match_crd_namespace_obj())
  263. m_get_knp_crd.assert_called_once()
  264. m_delete_sg_rule.assert_called_once_with(sg_rule_id)
  265. m_patch_kuryr_crd.assert_called_once()
  266. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  267. 'patch_kuryr_crd')
  268. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  269. 'delete_security_group_rule')
  270. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  271. 'get_kuryrnetpolicy_crds')
  272. def test_delete_namespace_sg_rule_no_match(self, m_get_knp_crd,
  273. m_delete_sg_rule,
  274. m_patch_kuryr_crd):
  275. cls = namespace_security_groups.NamespacePodSecurityGroupsDriver
  276. m_driver = mock.MagicMock(spec=cls)
  277. m_get_knp_crd.return_value = {"items": [get_matched_crd_obj()]}
  278. cls.delete_namespace_sg_rules(m_driver,
  279. get_no_match_crd_namespace_obj())
  280. m_get_knp_crd.assert_called_once()
  281. m_delete_sg_rule.assert_not_called()
  282. m_patch_kuryr_crd.assert_not_called()
  283. @mock.patch('kuryr_kubernetes.controller.drivers.'
  284. 'namespace_security_groups._create_sg_rule')
  285. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  286. 'match_selector')
  287. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  288. 'get_namespace_subnet_cidr')
  289. def test__parse_rules(self, m_get_ns_subnet_cidr, m_match_selector,
  290. m_create_sg_rule):
  291. crd = get_crd_obj_no_match()
  292. policy = crd['spec']['networkpolicy_spec']
  293. i_rule = policy.get('ingress')[0]
  294. ns_selector = i_rule['from'][0].get('namespaceSelector')
  295. ns = get_match_crd_namespace_obj()
  296. m_get_ns_subnet_cidr.return_value = '10.0.2.0/26'
  297. m_match_selector.return_value = True
  298. m_create_sg_rule.return_value = get_sg_rule()
  299. matched, rules = namespace_security_groups._parse_rules('ingress',
  300. crd, ns)
  301. m_get_ns_subnet_cidr.assert_called_once_with(ns)
  302. m_match_selector.assert_called_once_with(ns_selector,
  303. ns['metadata']['labels'])
  304. m_create_sg_rule.assert_called_once()
  305. self.assertEqual(matched, True)
  306. self.assertEqual(rules, [get_sg_rule()])
  307. @mock.patch('kuryr_kubernetes.controller.drivers.'
  308. 'namespace_security_groups._create_sg_rule')
  309. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  310. 'match_selector')
  311. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  312. 'get_namespace_subnet_cidr')
  313. def test__parse_rules_no_match(self, m_get_ns_subnet_cidr,
  314. m_match_selector, m_create_sg_rule):
  315. crd = get_crd_obj_no_match()
  316. policy = crd['spec']['networkpolicy_spec']
  317. i_rule = policy.get('ingress')[0]
  318. ns_selector = i_rule['from'][0].get('namespaceSelector')
  319. ns = get_no_match_crd_namespace_obj()
  320. m_get_ns_subnet_cidr.return_value = '10.0.2.0/26'
  321. m_match_selector.return_value = False
  322. matched, rules = namespace_security_groups._parse_rules('ingress',
  323. crd, ns)
  324. m_get_ns_subnet_cidr.assert_called_once_with(ns)
  325. m_match_selector.assert_called_once_with(ns_selector,
  326. ns['metadata']['labels'])
  327. m_create_sg_rule.assert_not_called()
  328. self.assertEqual(matched, False)
  329. self.assertEqual(rules, [])
  330. @mock.patch('kuryr_kubernetes.controller.drivers.'
  331. 'namespace_security_groups._create_sg_rule')
  332. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  333. 'get_pod_ip')
  334. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  335. 'get_pods')
  336. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  337. 'match_selector')
  338. @mock.patch('kuryr_kubernetes.controller.drivers.utils.'
  339. 'get_namespace_subnet_cidr')
  340. def test__parse_rules_all_selectors(self, m_get_ns_subnet_cidr,
  341. m_match_selector, m_get_pods,
  342. m_get_pod_ip, m_create_sg_rule):
  343. crd = get_crd_obj_with_all_selectors()
  344. policy = crd['spec']['networkpolicy_spec']
  345. i_rule = policy.get('ingress')[0]
  346. ns_selector = i_rule['from'][0].get('namespaceSelector')
  347. pod_selector = i_rule['from'][0].get('podSelector')
  348. ns = get_match_crd_namespace_obj()
  349. pod = get_match_crd_pod_obj()
  350. m_get_ns_subnet_cidr.return_value = '10.0.2.0/26'
  351. m_match_selector.return_value = True
  352. m_get_pods.return_value = {"items": [pod]}
  353. m_get_pod_ip.return_value = pod['status']['podIP']
  354. m_create_sg_rule.return_value = get_sg_rule()
  355. matched, rules = namespace_security_groups._parse_rules('ingress',
  356. crd, ns)
  357. m_get_ns_subnet_cidr.assert_called_once_with(ns)
  358. m_match_selector.assert_called_once_with(ns_selector,
  359. ns['metadata']['labels'])
  360. m_get_pods.assert_called_once_with(pod_selector,
  361. ns['metadata']['name'])
  362. m_get_pod_ip.assert_called_once_with(pod)
  363. m_create_sg_rule.assert_called_once()
  364. self.assertEqual(matched, True)
  365. self.assertEqual(rules, [get_sg_rule()])