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.

utils.py 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. # Copyright (c) 2018 Samsung Electronics Co.,Ltd
  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. from oslo_cache import core as cache
  16. from oslo_config import cfg
  17. from oslo_log import log
  18. from oslo_serialization import jsonutils
  19. from six.moves.urllib import parse
  20. from kuryr_kubernetes import clients
  21. from kuryr_kubernetes import constants
  22. from kuryr_kubernetes import exceptions as k_exc
  23. from kuryr_kubernetes import os_vif_util as ovu
  24. from kuryr_kubernetes import utils
  25. from neutronclient.common import exceptions as n_exc
  26. OPERATORS_WITH_VALUES = [constants.K8S_OPERATOR_IN,
  27. constants.K8S_OPERATOR_NOT_IN]
  28. LOG = log.getLogger(__name__)
  29. CONF = cfg.CONF
  30. pod_ip_caching_opts = [
  31. cfg.BoolOpt('caching', default=True),
  32. cfg.IntOpt('cache_time', default=3600),
  33. ]
  34. CONF.register_opts(pod_ip_caching_opts, "pod_ip_caching")
  35. cache.configure(CONF)
  36. pod_ip_cache_region = cache.create_region()
  37. MEMOIZE = cache.get_memoization_decorator(
  38. CONF, pod_ip_cache_region, "pod_ip_caching")
  39. cache.configure_cache_region(CONF, pod_ip_cache_region)
  40. def get_network_id(subnets):
  41. ids = ovu.osvif_to_neutron_network_ids(subnets)
  42. if len(ids) != 1:
  43. raise k_exc.IntegrityError(
  44. "Subnet mapping %(subnets)s is not valid: "
  45. "%(num_networks)s unique networks found" %
  46. {'subnets': subnets, 'num_networks': len(ids)})
  47. return ids[0]
  48. def get_port_name(pod):
  49. return "%(namespace)s/%(name)s" % pod['metadata']
  50. def get_device_id(pod):
  51. return pod['metadata']['uid']
  52. def get_host_id(pod):
  53. return pod['spec']['nodeName']
  54. def get_pod_state(pod):
  55. try:
  56. annotations = pod['metadata']['annotations']
  57. state_annotation = annotations[constants.K8S_ANNOTATION_VIF]
  58. except KeyError:
  59. return None
  60. state_annotation = jsonutils.loads(state_annotation)
  61. state = utils.extract_pod_annotation(state_annotation)
  62. return state
  63. def is_host_network(pod):
  64. return pod['spec'].get('hostNetwork', False)
  65. def get_pods(selector, namespace=None):
  66. """Return a k8s object list with the pods matching the selector.
  67. It accepts an optional parameter to state the namespace where the pod
  68. selector will be apply. If empty namespace is passed, then the pod
  69. selector is applied in all namespaces.
  70. param selector: k8s selector of types matchLabels or matchExpressions
  71. param namespace: namespace name where the selector will be applied. If
  72. None, the pod selector is applied in all namespaces
  73. return: k8s list object containing all matching pods
  74. """
  75. kubernetes = clients.get_kubernetes_client()
  76. svc_selector = selector.get('selector')
  77. if svc_selector:
  78. labels = replace_encoded_characters(svc_selector)
  79. else:
  80. labels = selector.get('matchLabels', None)
  81. if labels:
  82. # Removing pod-template-hash as pods will not have it and
  83. # otherwise there will be no match
  84. labels.pop('pod-template-hash', None)
  85. labels = replace_encoded_characters(labels)
  86. exps = selector.get('matchExpressions', None)
  87. if exps:
  88. exps = ', '.join(format_expression(exp) for exp in exps)
  89. if labels:
  90. expressions = parse.quote("," + exps)
  91. labels += expressions
  92. else:
  93. labels = parse.quote(exps)
  94. if namespace:
  95. pods = kubernetes.get(
  96. '{}/namespaces/{}/pods?labelSelector={}'.format(
  97. constants.K8S_API_BASE, namespace, labels))
  98. else:
  99. pods = kubernetes.get(
  100. '{}/pods?labelSelector={}'.format(constants.K8S_API_BASE, labels))
  101. return pods
  102. def get_namespaces(selector):
  103. """Return a k8s object list with the namespaces matching the selector.
  104. param selector: k8s selector of types matchLabels or matchExpressions
  105. return: k8s list object containing all matching namespaces
  106. """
  107. kubernetes = clients.get_kubernetes_client()
  108. labels = selector.get('matchLabels', None)
  109. if labels:
  110. labels = replace_encoded_characters(labels)
  111. exps = selector.get('matchExpressions', None)
  112. if exps:
  113. exps = ', '.join(format_expression(exp) for exp in exps)
  114. if labels:
  115. expressions = parse.quote("," + exps)
  116. labels += expressions
  117. else:
  118. labels = parse.quote(exps)
  119. namespaces = kubernetes.get(
  120. '{}/namespaces?labelSelector={}'.format(
  121. constants.K8S_API_BASE, labels))
  122. return namespaces
  123. def format_expression(expression):
  124. key = expression['key']
  125. operator = expression['operator'].lower()
  126. if operator in OPERATORS_WITH_VALUES:
  127. values = expression['values']
  128. values = str(', '.join(values))
  129. values = "(%s)" % values
  130. return "%s %s %s" % (key, operator, values)
  131. else:
  132. if operator == constants.K8S_OPERATOR_DOES_NOT_EXIST:
  133. return "!%s" % key
  134. else:
  135. return key
  136. def replace_encoded_characters(labels):
  137. labels = parse.urlencode(labels)
  138. # NOTE(ltomasbo): K8s API does not accept &, so we need to AND
  139. # the matchLabels with ',' or '%2C' instead
  140. labels = labels.replace('&', ',')
  141. return labels
  142. def create_security_group_rule(body):
  143. neutron = clients.get_neutron_client()
  144. sgr = ''
  145. try:
  146. sgr = neutron.create_security_group_rule(
  147. body=body)
  148. except n_exc.Conflict as ex:
  149. LOG.debug("Failed to create already existing security group "
  150. "rule %s", body)
  151. # Get existent sg rule id from exception message
  152. sgr_id = str(ex).split("Rule id is", 1)[1].split()[0][:-1]
  153. return sgr_id
  154. except n_exc.NeutronClientException:
  155. LOG.debug("Error creating security group rule")
  156. raise
  157. return sgr["security_group_rule"]["id"]
  158. def delete_security_group_rule(security_group_rule_id):
  159. neutron = clients.get_neutron_client()
  160. try:
  161. LOG.debug("Deleting sg rule with ID: %s", security_group_rule_id)
  162. neutron.delete_security_group_rule(
  163. security_group_rule=security_group_rule_id)
  164. except n_exc.NotFound:
  165. LOG.debug("Error deleting security group rule as it does not "
  166. "exist: %s", security_group_rule_id)
  167. except n_exc.NeutronClientException:
  168. LOG.debug("Error deleting security group rule: %s",
  169. security_group_rule_id)
  170. raise
  171. def patch_kuryr_crd(crd, i_rules, e_rules, pod_selector, np_spec=None):
  172. kubernetes = clients.get_kubernetes_client()
  173. crd_name = crd['metadata']['name']
  174. if not np_spec:
  175. np_spec = crd['spec']['networkpolicy_spec']
  176. LOG.debug('Patching KuryrNetPolicy CRD %s' % crd_name)
  177. try:
  178. kubernetes.patch('spec', crd['metadata']['selfLink'],
  179. {'ingressSgRules': i_rules,
  180. 'egressSgRules': e_rules,
  181. 'podSelector': pod_selector,
  182. 'networkpolicy_spec': np_spec})
  183. except k_exc.K8sClientException:
  184. LOG.exception('Error updating kuryrnetpolicy CRD %s', crd_name)
  185. raise
  186. def create_security_group_rule_body(
  187. security_group_id, direction, port_range_min,
  188. port_range_max=None, protocol=None, ethertype='IPv4', cidr=None,
  189. description="Kuryr-Kubernetes NetPolicy SG rule"):
  190. if not port_range_min:
  191. port_range_min = 1
  192. port_range_max = 65535
  193. elif not port_range_max:
  194. port_range_max = port_range_min
  195. if not protocol:
  196. protocol = 'TCP'
  197. security_group_rule_body = {
  198. u'security_group_rule': {
  199. u'ethertype': ethertype,
  200. u'security_group_id': security_group_id,
  201. u'description': description,
  202. u'direction': direction,
  203. u'protocol': protocol.lower(),
  204. u'port_range_min': port_range_min,
  205. u'port_range_max': port_range_max,
  206. }
  207. }
  208. if cidr:
  209. security_group_rule_body[u'security_group_rule'][
  210. u'remote_ip_prefix'] = cidr
  211. LOG.debug("Creating sg rule body %s", security_group_rule_body)
  212. return security_group_rule_body
  213. @MEMOIZE
  214. def get_pod_ip(pod):
  215. vif = pod['metadata']['annotations'].get('openstack.org/kuryr-vif')
  216. if vif is None:
  217. return vif
  218. vif = jsonutils.loads(vif)
  219. vif = vif['versioned_object.data']['default_vif']
  220. network = (vif['versioned_object.data']['network']
  221. ['versioned_object.data'])
  222. first_subnet = (network['subnets']['versioned_object.data']
  223. ['objects'][0]['versioned_object.data'])
  224. first_subnet_ip = (first_subnet['ips']['versioned_object.data']
  225. ['objects'][0]['versioned_object.data']['address'])
  226. return first_subnet_ip
  227. def get_pod_annotated_labels(pod):
  228. try:
  229. annotations = pod['metadata']['annotations']
  230. pod_labels_annotation = annotations[constants.K8S_ANNOTATION_LABEL]
  231. except KeyError:
  232. return None
  233. pod_labels = jsonutils.loads(pod_labels_annotation)
  234. return pod_labels