OpenStack Networking (Neutron)
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.
 
 
 
 

308 lines
11 KiB

  1. # Copyright 2014 OpenStack Foundation.
  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 errno
  16. import grp
  17. import os
  18. import pwd
  19. from neutron_lib.callbacks import events
  20. from neutron_lib.callbacks import registry
  21. from neutron_lib.callbacks import resources
  22. from neutron_lib import constants
  23. from neutron_lib import exceptions
  24. from oslo_config import cfg
  25. from oslo_log import log as logging
  26. from neutron._i18n import _
  27. from neutron.agent.l3 import ha_router
  28. from neutron.agent.l3 import namespaces
  29. from neutron.agent.linux import external_process
  30. LOG = logging.getLogger(__name__)
  31. METADATA_SERVICE_NAME = 'metadata-proxy'
  32. HAPROXY_SERVICE = 'haproxy'
  33. PROXY_CONFIG_DIR = "ns-metadata-proxy"
  34. _HAPROXY_CONFIG_TEMPLATE = """
  35. global
  36. log /dev/log local0 %(log_level)s
  37. log-tag %(log_tag)s
  38. user %(user)s
  39. group %(group)s
  40. maxconn 1024
  41. pidfile %(pidfile)s
  42. daemon
  43. defaults
  44. log global
  45. mode http
  46. option httplog
  47. option dontlognull
  48. option http-server-close
  49. option forwardfor
  50. retries 3
  51. timeout http-request 30s
  52. timeout connect 30s
  53. timeout client 32s
  54. timeout server 32s
  55. timeout http-keep-alive 30s
  56. listen listener
  57. bind %(host)s:%(port)s
  58. server metadata %(unix_socket_path)s
  59. http-request add-header X-Neutron-%(res_type)s-ID %(res_id)s
  60. """
  61. class InvalidUserOrGroupException(Exception):
  62. pass
  63. class HaproxyConfigurator(object):
  64. def __init__(self, network_id, router_id, unix_socket_path, host, port,
  65. user, group, state_path, pid_file):
  66. self.network_id = network_id
  67. self.router_id = router_id
  68. if network_id is None and router_id is None:
  69. raise exceptions.NetworkIdOrRouterIdRequiredError()
  70. self.host = host
  71. self.port = port
  72. self.user = user
  73. self.group = group
  74. self.state_path = state_path
  75. self.unix_socket_path = unix_socket_path
  76. self.pidfile = pid_file
  77. self.log_level = (
  78. 'debug' if logging.is_debug_enabled(cfg.CONF) else 'info')
  79. # log-tag will cause entries to have the string pre-pended, so use
  80. # the uuid haproxy will be started with. Additionally, if it
  81. # starts with "haproxy" then things will get logged to
  82. # /var/log/haproxy.log on Debian distros, instead of to syslog.
  83. uuid = network_id or router_id
  84. self.log_tag = "haproxy-" + METADATA_SERVICE_NAME + "-" + uuid
  85. def create_config_file(self):
  86. """Create the config file for haproxy."""
  87. # Need to convert uid/gid into username/group
  88. try:
  89. username = pwd.getpwuid(int(self.user)).pw_name
  90. except (ValueError, KeyError):
  91. try:
  92. username = pwd.getpwnam(self.user).pw_name
  93. except KeyError:
  94. raise InvalidUserOrGroupException(
  95. _("Invalid user/uid: '%s'") % self.user)
  96. try:
  97. groupname = grp.getgrgid(int(self.group)).gr_name
  98. except (ValueError, KeyError):
  99. try:
  100. groupname = grp.getgrnam(self.group).gr_name
  101. except KeyError:
  102. raise InvalidUserOrGroupException(
  103. _("Invalid group/gid: '%s'") % self.group)
  104. cfg_info = {
  105. 'host': self.host,
  106. 'port': self.port,
  107. 'unix_socket_path': self.unix_socket_path,
  108. 'user': username,
  109. 'group': groupname,
  110. 'pidfile': self.pidfile,
  111. 'log_level': self.log_level,
  112. 'log_tag': self.log_tag
  113. }
  114. if self.network_id:
  115. cfg_info['res_type'] = 'Network'
  116. cfg_info['res_id'] = self.network_id
  117. else:
  118. cfg_info['res_type'] = 'Router'
  119. cfg_info['res_id'] = self.router_id
  120. haproxy_cfg = _HAPROXY_CONFIG_TEMPLATE % cfg_info
  121. LOG.debug("haproxy_cfg = %s", haproxy_cfg)
  122. cfg_dir = self.get_config_path(self.state_path)
  123. # uuid has to be included somewhere in the command line so that it can
  124. # be tracked by process_monitor.
  125. self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id'])
  126. if not os.path.exists(cfg_dir):
  127. os.makedirs(cfg_dir)
  128. with open(self.cfg_path, "w") as cfg_file:
  129. cfg_file.write(haproxy_cfg)
  130. @staticmethod
  131. def get_config_path(state_path):
  132. return os.path.join(state_path or cfg.CONF.state_path,
  133. PROXY_CONFIG_DIR)
  134. @staticmethod
  135. def cleanup_config_file(uuid, state_path):
  136. """Delete config file created when metadata proxy was spawned."""
  137. # Delete config file if it exists
  138. cfg_path = os.path.join(
  139. HaproxyConfigurator.get_config_path(state_path),
  140. "%s.conf" % uuid)
  141. try:
  142. os.unlink(cfg_path)
  143. except OSError as ex:
  144. # It can happen that this function is called but metadata proxy
  145. # was never spawned so its config file won't exist
  146. if ex.errno != errno.ENOENT:
  147. raise
  148. class MetadataDriver(object):
  149. monitors = {}
  150. def __init__(self, l3_agent):
  151. self.metadata_port = l3_agent.conf.metadata_port
  152. self.metadata_access_mark = l3_agent.conf.metadata_access_mark
  153. registry.subscribe(
  154. after_router_added, resources.ROUTER, events.AFTER_CREATE)
  155. registry.subscribe(
  156. after_router_updated, resources.ROUTER, events.AFTER_UPDATE)
  157. registry.subscribe(
  158. before_router_removed, resources.ROUTER, events.BEFORE_DELETE)
  159. @classmethod
  160. def metadata_filter_rules(cls, port, mark):
  161. return [('INPUT', '-m mark --mark %s/%s -j ACCEPT' %
  162. (mark, constants.ROUTER_MARK_MASK)),
  163. ('INPUT', '-p tcp -m tcp --dport %s '
  164. '-j DROP' % port)]
  165. @classmethod
  166. def metadata_nat_rules(cls, port):
  167. return [('PREROUTING', '-d 169.254.169.254/32 '
  168. '-i %(interface_name)s '
  169. '-p tcp -m tcp --dport 80 -j REDIRECT '
  170. '--to-ports %(port)s' %
  171. {'interface_name': namespaces.INTERNAL_DEV_PREFIX + '+',
  172. 'port': port})]
  173. @classmethod
  174. def _get_metadata_proxy_user_group(cls, conf):
  175. user = conf.metadata_proxy_user or str(os.geteuid())
  176. group = conf.metadata_proxy_group or str(os.getegid())
  177. return user, group
  178. @classmethod
  179. def _get_metadata_proxy_callback(cls, bind_address, port, conf,
  180. network_id=None, router_id=None):
  181. def callback(pid_file):
  182. metadata_proxy_socket = conf.metadata_proxy_socket
  183. user, group = (
  184. cls._get_metadata_proxy_user_group(conf))
  185. haproxy = HaproxyConfigurator(network_id,
  186. router_id,
  187. metadata_proxy_socket,
  188. bind_address,
  189. port,
  190. user,
  191. group,
  192. conf.state_path,
  193. pid_file)
  194. haproxy.create_config_file()
  195. proxy_cmd = [HAPROXY_SERVICE,
  196. '-f', haproxy.cfg_path]
  197. return proxy_cmd
  198. return callback
  199. @classmethod
  200. def spawn_monitored_metadata_proxy(cls, monitor, ns_name, port, conf,
  201. bind_address="0.0.0.0", network_id=None,
  202. router_id=None):
  203. uuid = network_id or router_id
  204. callback = cls._get_metadata_proxy_callback(
  205. bind_address, port, conf, network_id=network_id,
  206. router_id=router_id)
  207. pm = cls._get_metadata_proxy_process_manager(uuid, conf,
  208. ns_name=ns_name,
  209. callback=callback)
  210. pm.enable()
  211. monitor.register(uuid, METADATA_SERVICE_NAME, pm)
  212. cls.monitors[router_id] = pm
  213. @classmethod
  214. def destroy_monitored_metadata_proxy(cls, monitor, uuid, conf, ns_name):
  215. monitor.unregister(uuid, METADATA_SERVICE_NAME)
  216. pm = cls._get_metadata_proxy_process_manager(uuid, conf,
  217. ns_name=ns_name)
  218. pm.disable()
  219. # Delete metadata proxy config file
  220. HaproxyConfigurator.cleanup_config_file(uuid, cfg.CONF.state_path)
  221. cls.monitors.pop(uuid, None)
  222. @classmethod
  223. def _get_metadata_proxy_process_manager(cls, router_id, conf, ns_name=None,
  224. callback=None):
  225. return external_process.ProcessManager(
  226. conf=conf,
  227. uuid=router_id,
  228. namespace=ns_name,
  229. service=HAPROXY_SERVICE,
  230. default_cmd_callback=callback)
  231. def after_router_added(resource, event, l3_agent, **kwargs):
  232. router = kwargs['router']
  233. proxy = l3_agent.metadata_driver
  234. for c, r in proxy.metadata_filter_rules(proxy.metadata_port,
  235. proxy.metadata_access_mark):
  236. router.iptables_manager.ipv4['filter'].add_rule(c, r)
  237. for c, r in proxy.metadata_nat_rules(proxy.metadata_port):
  238. router.iptables_manager.ipv4['nat'].add_rule(c, r)
  239. router.iptables_manager.apply()
  240. if not isinstance(router, ha_router.HaRouter):
  241. proxy.spawn_monitored_metadata_proxy(
  242. l3_agent.process_monitor,
  243. router.ns_name,
  244. proxy.metadata_port,
  245. l3_agent.conf,
  246. router_id=router.router_id)
  247. def after_router_updated(resource, event, l3_agent, **kwargs):
  248. router = kwargs['router']
  249. proxy = l3_agent.metadata_driver
  250. if (not proxy.monitors.get(router.router_id) and
  251. not isinstance(router, ha_router.HaRouter)):
  252. proxy.spawn_monitored_metadata_proxy(
  253. l3_agent.process_monitor,
  254. router.ns_name,
  255. proxy.metadata_port,
  256. l3_agent.conf,
  257. router_id=router.router_id)
  258. def before_router_removed(resource, event, l3_agent, payload=None):
  259. router = payload.latest_state
  260. proxy = l3_agent.metadata_driver
  261. proxy.destroy_monitored_metadata_proxy(l3_agent.process_monitor,
  262. router.router['id'],
  263. l3_agent.conf,
  264. router.ns_name)