Merge "Make common Metadata Driver classes"
This commit is contained in:
commit
40815c7086
|
@ -13,164 +13,36 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import grp
|
||||
import os
|
||||
import pwd
|
||||
import signal
|
||||
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import registry
|
||||
from neutron_lib.callbacks import resources
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import exceptions
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.agent.l3 import ha_router
|
||||
from neutron.agent.l3 import namespaces
|
||||
from neutron.agent.linux import external_process
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import utils as linux_utils
|
||||
from neutron.agent.metadata import driver_base
|
||||
from neutron.common import coordination
|
||||
from neutron.common import metadata as comm_meta
|
||||
from neutron.common import utils as common_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SIGTERM_TIMEOUT = 5
|
||||
|
||||
METADATA_SERVICE_NAME = 'metadata-proxy'
|
||||
HAPROXY_SERVICE = 'haproxy'
|
||||
|
||||
PROXY_CONFIG_DIR = "ns-metadata-proxy"
|
||||
|
||||
_HEADER_CONFIG_TEMPLATE = """
|
||||
http-request del-header X-Neutron-%(res_type_del)s-ID
|
||||
http-request set-header X-Neutron-%(res_type)s-ID %(res_id)s
|
||||
"""
|
||||
|
||||
_UNLIMITED_CONFIG_TEMPLATE = """
|
||||
listen listener
|
||||
bind %(host)s:%(port)s
|
||||
%(bind_v6_line)s
|
||||
server metadata %(unix_socket_path)s
|
||||
"""
|
||||
|
||||
class HaproxyConfigurator(driver_base.HaproxyConfiguratorBase):
|
||||
PROXY_CONFIG_DIR = "ns-metadata-proxy"
|
||||
HEADER_CONFIG_TEMPLATE = _HEADER_CONFIG_TEMPLATE
|
||||
|
||||
|
||||
class HaproxyConfigurator(object):
|
||||
def __init__(self, network_id, router_id, unix_socket_path, host, port,
|
||||
user, group, state_path, pid_file, rate_limiting_config,
|
||||
host_v6=None, bind_interface=None):
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
if network_id is None and router_id is None:
|
||||
raise exceptions.NetworkIdOrRouterIdRequiredError()
|
||||
|
||||
self.host = host
|
||||
self.host_v6 = host_v6
|
||||
self.bind_interface = bind_interface
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.group = group
|
||||
self.state_path = state_path
|
||||
self.unix_socket_path = unix_socket_path
|
||||
self.pidfile = pid_file
|
||||
self.rate_limiting_config = rate_limiting_config
|
||||
self.log_level = (
|
||||
'debug' if logging.is_debug_enabled(cfg.CONF) else 'info')
|
||||
# log-tag will cause entries to have the string pre-pended, so use
|
||||
# the uuid haproxy will be started with. Additionally, if it
|
||||
# starts with "haproxy" then things will get logged to
|
||||
# /var/log/haproxy.log on Debian distros, instead of to syslog.
|
||||
uuid = network_id or router_id
|
||||
self.log_tag = "haproxy-" + METADATA_SERVICE_NAME + "-" + uuid
|
||||
|
||||
def create_config_file(self):
|
||||
"""Create the config file for haproxy."""
|
||||
# Need to convert uid/gid into username/group
|
||||
try:
|
||||
username = pwd.getpwuid(int(self.user)).pw_name
|
||||
except (ValueError, KeyError):
|
||||
try:
|
||||
username = pwd.getpwnam(self.user).pw_name
|
||||
except KeyError:
|
||||
raise comm_meta.InvalidUserOrGroupException(
|
||||
_("Invalid user/uid: '%s'") % self.user)
|
||||
|
||||
try:
|
||||
groupname = grp.getgrgid(int(self.group)).gr_name
|
||||
except (ValueError, KeyError):
|
||||
try:
|
||||
groupname = grp.getgrnam(self.group).gr_name
|
||||
except KeyError:
|
||||
raise comm_meta.InvalidUserOrGroupException(
|
||||
_("Invalid group/gid: '%s'") % self.group)
|
||||
|
||||
cfg_info = {
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'unix_socket_path': self.unix_socket_path,
|
||||
'user': username,
|
||||
'group': groupname,
|
||||
'pidfile': self.pidfile,
|
||||
'log_level': self.log_level,
|
||||
'log_tag': self.log_tag,
|
||||
'bind_v6_line': '',
|
||||
}
|
||||
if self.host_v6 and self.bind_interface:
|
||||
cfg_info['bind_v6_line'] = (
|
||||
'bind %s:%s interface %s' % (
|
||||
self.host_v6, self.port, self.bind_interface)
|
||||
)
|
||||
# If using the network ID, delete any spurious router ID that might
|
||||
# have been in the request, same for network ID when using router ID.
|
||||
if self.network_id:
|
||||
cfg_info['res_type'] = 'Network'
|
||||
cfg_info['res_id'] = self.network_id
|
||||
cfg_info['res_type_del'] = 'Router'
|
||||
else:
|
||||
cfg_info['res_type'] = 'Router'
|
||||
cfg_info['res_id'] = self.router_id
|
||||
cfg_info['res_type_del'] = 'Network'
|
||||
|
||||
haproxy_cfg = comm_meta.get_haproxy_config(cfg_info,
|
||||
self.rate_limiting_config,
|
||||
_HEADER_CONFIG_TEMPLATE,
|
||||
_UNLIMITED_CONFIG_TEMPLATE)
|
||||
|
||||
LOG.debug("haproxy_cfg = %s", haproxy_cfg)
|
||||
cfg_dir = self.get_config_path(self.state_path)
|
||||
# uuid has to be included somewhere in the command line so that it can
|
||||
# be tracked by process_monitor.
|
||||
self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id'])
|
||||
if not os.path.exists(cfg_dir):
|
||||
os.makedirs(cfg_dir)
|
||||
with open(self.cfg_path, "w") as cfg_file:
|
||||
cfg_file.write(haproxy_cfg)
|
||||
|
||||
@staticmethod
|
||||
def get_config_path(state_path):
|
||||
return os.path.join(state_path or cfg.CONF.state_path,
|
||||
PROXY_CONFIG_DIR)
|
||||
|
||||
@staticmethod
|
||||
def cleanup_config_file(uuid, state_path):
|
||||
"""Delete config file created when metadata proxy was spawned."""
|
||||
# Delete config file if it exists
|
||||
cfg_path = os.path.join(
|
||||
HaproxyConfigurator.get_config_path(state_path),
|
||||
"%s.conf" % uuid)
|
||||
linux_utils.delete_if_exists(cfg_path, run_as_root=True)
|
||||
|
||||
|
||||
class MetadataDriver(object):
|
||||
|
||||
monitors = {}
|
||||
|
||||
def __init__(self, l3_agent):
|
||||
class MetadataDriver(driver_base.MetadataDriverBase):
|
||||
def __init__(self, l3_agent=None):
|
||||
if not l3_agent:
|
||||
return
|
||||
self.metadata_port = l3_agent.conf.metadata_port
|
||||
self.metadata_access_mark = l3_agent.conf.metadata_access_mark
|
||||
registry.subscribe(
|
||||
|
@ -180,142 +52,26 @@ class MetadataDriver(object):
|
|||
registry.subscribe(
|
||||
before_router_removed, resources.ROUTER, events.BEFORE_DELETE)
|
||||
|
||||
@classmethod
|
||||
def metadata_filter_rules(cls, port, mark):
|
||||
return [('INPUT', '-m mark --mark %s/%s -j ACCEPT' %
|
||||
(mark, constants.ROUTER_MARK_MASK)),
|
||||
('INPUT', '-p tcp -m tcp --dport %s '
|
||||
'-j DROP' % port)]
|
||||
@staticmethod
|
||||
def haproxy_configurator():
|
||||
return HaproxyConfigurator
|
||||
|
||||
@classmethod
|
||||
def metadata_nat_rules(
|
||||
cls, port, metadata_address=constants.METADATA_V4_CIDR):
|
||||
return [('PREROUTING', '-d %(metadata_address)s '
|
||||
'-i %(interface_name)s '
|
||||
'-p tcp -m tcp --dport 80 -j REDIRECT '
|
||||
'--to-ports %(port)s' %
|
||||
{'metadata_address': metadata_address,
|
||||
'interface_name': namespaces.INTERNAL_DEV_PREFIX + '+',
|
||||
'port': port})]
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_user_group(cls, conf):
|
||||
user = conf.metadata_proxy_user or str(os.geteuid())
|
||||
group = conf.metadata_proxy_group or str(os.getegid())
|
||||
def metadata_filter_rules(port, mark):
|
||||
return [('INPUT', '-m mark --mark %s/%s -j ACCEPT' %
|
||||
(mark, constants.ROUTER_MARK_MASK)),
|
||||
('INPUT', '-p tcp -m tcp --dport %s '
|
||||
'-j DROP' % port)]
|
||||
|
||||
return user, group
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_callback(cls, bind_address, port, conf,
|
||||
network_id=None, router_id=None,
|
||||
bind_address_v6=None,
|
||||
bind_interface=None):
|
||||
def callback(pid_file):
|
||||
metadata_proxy_socket = conf.metadata_proxy_socket
|
||||
user, group = (
|
||||
cls._get_metadata_proxy_user_group(conf))
|
||||
haproxy = HaproxyConfigurator(network_id,
|
||||
router_id,
|
||||
metadata_proxy_socket,
|
||||
bind_address,
|
||||
port,
|
||||
user,
|
||||
group,
|
||||
conf.state_path,
|
||||
pid_file,
|
||||
conf.metadata_rate_limiting,
|
||||
bind_address_v6,
|
||||
bind_interface)
|
||||
haproxy.create_config_file()
|
||||
proxy_cmd = [HAPROXY_SERVICE,
|
||||
'-f', haproxy.cfg_path]
|
||||
return proxy_cmd
|
||||
|
||||
return callback
|
||||
|
||||
@classmethod
|
||||
def spawn_monitored_metadata_proxy(cls, monitor, ns_name, port, conf,
|
||||
bind_address="0.0.0.0", network_id=None,
|
||||
router_id=None, bind_address_v6=None,
|
||||
bind_interface=None):
|
||||
if bind_interface is not None and bind_address_v6 is not None:
|
||||
# HAProxy cannot bind() until IPv6 Duplicate Address Detection
|
||||
# completes. We must wait until the address leaves its 'tentative'
|
||||
# state.
|
||||
try:
|
||||
ip_lib.IpAddrCommand(
|
||||
parent=ip_lib.IPDevice(name=bind_interface,
|
||||
namespace=ns_name)
|
||||
).wait_until_address_ready(address=bind_address_v6)
|
||||
except ip_lib.DADFailed as exc:
|
||||
# This failure means that another DHCP agent has already
|
||||
# configured this metadata address, so all requests will
|
||||
# be via that single agent.
|
||||
LOG.info('DAD failed for address %(address)s on interface '
|
||||
'%(interface)s in namespace %(namespace)s on network '
|
||||
'%(network)s, deleting it. Exception: %(exception)s',
|
||||
{'address': bind_address_v6,
|
||||
'interface': bind_interface,
|
||||
'namespace': ns_name,
|
||||
'network': network_id,
|
||||
'exception': str(exc)})
|
||||
try:
|
||||
ip_lib.delete_ip_address(bind_address_v6, bind_interface,
|
||||
namespace=ns_name)
|
||||
except Exception as exc:
|
||||
# do not re-raise a delete failure, just log
|
||||
LOG.info('Address deletion failure: %s', str(exc))
|
||||
|
||||
# Do not use the address or interface when DAD fails
|
||||
bind_address_v6 = bind_interface = None
|
||||
|
||||
uuid = network_id or router_id
|
||||
callback = cls._get_metadata_proxy_callback(
|
||||
bind_address, port, conf,
|
||||
network_id=network_id, router_id=router_id,
|
||||
bind_address_v6=bind_address_v6, bind_interface=bind_interface)
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name,
|
||||
callback=callback)
|
||||
try:
|
||||
pm.enable(ensure_active=True)
|
||||
except exceptions.ProcessExecutionError as exec_err:
|
||||
LOG.error("Encountered process execution error %(err)s while "
|
||||
"starting process in namespace %(ns)s",
|
||||
{"err": exec_err, "ns": ns_name})
|
||||
return
|
||||
monitor.register(uuid, METADATA_SERVICE_NAME, pm)
|
||||
cls.monitors[router_id] = pm
|
||||
|
||||
@classmethod
|
||||
def destroy_monitored_metadata_proxy(cls, monitor, uuid, conf, ns_name):
|
||||
monitor.unregister(uuid, METADATA_SERVICE_NAME)
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name)
|
||||
pm.disable(sig=str(int(signal.SIGTERM)))
|
||||
try:
|
||||
common_utils.wait_until_true(lambda: not pm.active,
|
||||
timeout=SIGTERM_TIMEOUT)
|
||||
except common_utils.WaitTimeout:
|
||||
LOG.warning('Metadata process %s did not finish after SIGTERM '
|
||||
'signal in %s seconds, sending SIGKILL signal',
|
||||
pm.pid, SIGTERM_TIMEOUT)
|
||||
pm.disable(sig=str(int(signal.SIGKILL)))
|
||||
|
||||
# Delete metadata proxy config.
|
||||
HaproxyConfigurator.cleanup_config_file(uuid, cfg.CONF.state_path)
|
||||
|
||||
cls.monitors.pop(uuid, None)
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_process_manager(cls, router_id, conf, ns_name=None,
|
||||
callback=None):
|
||||
return external_process.ProcessManager(
|
||||
conf=conf,
|
||||
uuid=router_id,
|
||||
namespace=ns_name,
|
||||
service=HAPROXY_SERVICE,
|
||||
default_cmd_callback=callback)
|
||||
def metadata_nat_rules(port, metadata_address=constants.METADATA_V4_CIDR):
|
||||
return [('PREROUTING', '-d %(metadata_address)s '
|
||||
'-i %(interface_name)s '
|
||||
'-p tcp -m tcp --dport 80 -j REDIRECT '
|
||||
'--to-ports %(port)s' %
|
||||
{'metadata_address': metadata_address,
|
||||
'interface_name': namespaces.INTERNAL_DEV_PREFIX + '+',
|
||||
'port': port})]
|
||||
|
||||
|
||||
def after_router_added(resource, event, l3_agent, payload):
|
||||
|
@ -364,17 +120,17 @@ def before_router_removed(resource, event, l3_agent, payload=None):
|
|||
|
||||
@coordination.synchronized('router-lock-ns-{router.ns_name}')
|
||||
def apply_metadata_nat_rules(router, proxy):
|
||||
for c, r in proxy.metadata_filter_rules(proxy.metadata_port,
|
||||
proxy.metadata_access_mark):
|
||||
for c, r in metadata_filter_rules(proxy.metadata_port,
|
||||
proxy.metadata_access_mark):
|
||||
router.iptables_manager.ipv4['filter'].add_rule(c, r)
|
||||
if netutils.is_ipv6_enabled():
|
||||
for c, r in proxy.metadata_filter_rules(proxy.metadata_port,
|
||||
proxy.metadata_access_mark):
|
||||
for c, r in metadata_filter_rules(proxy.metadata_port,
|
||||
proxy.metadata_access_mark):
|
||||
router.iptables_manager.ipv6['filter'].add_rule(c, r)
|
||||
for c, r in proxy.metadata_nat_rules(proxy.metadata_port):
|
||||
for c, r in metadata_nat_rules(proxy.metadata_port):
|
||||
router.iptables_manager.ipv4['nat'].add_rule(c, r)
|
||||
if netutils.is_ipv6_enabled():
|
||||
for c, r in proxy.metadata_nat_rules(
|
||||
for c, r in metadata_nat_rules(
|
||||
proxy.metadata_port,
|
||||
metadata_address=(constants.METADATA_V6_CIDR)):
|
||||
router.iptables_manager.ipv6['nat'].add_rule(c, r)
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
# Copyright 2014 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import abc
|
||||
import grp
|
||||
import os
|
||||
import pwd
|
||||
import signal
|
||||
|
||||
from neutron_lib import exceptions
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.agent.linux import external_process
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import utils as linux_utils
|
||||
from neutron.common import metadata as comm_meta
|
||||
from neutron.common import utils as common_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SIGTERM_TIMEOUT = 5
|
||||
|
||||
METADATA_SERVICE_NAME = 'metadata-proxy'
|
||||
HAPROXY_SERVICE = 'haproxy'
|
||||
|
||||
_UNLIMITED_CONFIG_TEMPLATE = """
|
||||
listen listener
|
||||
bind %(host)s:%(port)s
|
||||
%(bind_v6_line)s
|
||||
server metadata %(unix_socket_path)s
|
||||
"""
|
||||
|
||||
|
||||
class HaproxyConfiguratorBase(object):
|
||||
PROXY_CONFIG_DIR = None
|
||||
HEADER_CONFIG_TEMPLATE = None
|
||||
|
||||
def __init__(self, network_id, router_id, unix_socket_path, host, port,
|
||||
user, group, state_path, pid_file, rate_limiting_config,
|
||||
host_v6=None, bind_interface=None):
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
if network_id is None and router_id is None:
|
||||
raise exceptions.NetworkIdOrRouterIdRequiredError()
|
||||
|
||||
self.host = host
|
||||
self.host_v6 = host_v6
|
||||
self.bind_interface = bind_interface
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.group = group
|
||||
self.state_path = state_path
|
||||
self.unix_socket_path = unix_socket_path
|
||||
self.pidfile = pid_file
|
||||
self.rate_limiting_config = rate_limiting_config
|
||||
self.log_level = (
|
||||
'debug' if logging.is_debug_enabled(cfg.CONF) else 'info')
|
||||
# log-tag will cause entries to have the string pre-pended, so use
|
||||
# the uuid haproxy will be started with. Additionally, if it
|
||||
# starts with "haproxy" then things will get logged to
|
||||
# /var/log/haproxy.log on Debian distros, instead of to syslog.
|
||||
uuid = network_id or router_id
|
||||
self.log_tag = "haproxy-{}-{}".format(METADATA_SERVICE_NAME, uuid)
|
||||
|
||||
def create_config_file(self):
|
||||
"""Create the config file for haproxy."""
|
||||
# Need to convert uid/gid into username/group
|
||||
try:
|
||||
username = pwd.getpwuid(int(self.user)).pw_name
|
||||
except (ValueError, KeyError):
|
||||
try:
|
||||
username = pwd.getpwnam(self.user).pw_name
|
||||
except KeyError:
|
||||
raise comm_meta.InvalidUserOrGroupException(
|
||||
_("Invalid user/uid: '%s'") % self.user)
|
||||
|
||||
try:
|
||||
groupname = grp.getgrgid(int(self.group)).gr_name
|
||||
except (ValueError, KeyError):
|
||||
try:
|
||||
groupname = grp.getgrnam(self.group).gr_name
|
||||
except KeyError:
|
||||
raise comm_meta.InvalidUserOrGroupException(
|
||||
_("Invalid group/gid: '%s'") % self.group)
|
||||
|
||||
cfg_info = {
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'unix_socket_path': self.unix_socket_path,
|
||||
'user': username,
|
||||
'group': groupname,
|
||||
'pidfile': self.pidfile,
|
||||
'log_level': self.log_level,
|
||||
'log_tag': self.log_tag,
|
||||
'bind_v6_line': '',
|
||||
}
|
||||
if self.host_v6 and self.bind_interface:
|
||||
cfg_info['bind_v6_line'] = (
|
||||
'bind %s:%s interface %s' % (
|
||||
self.host_v6, self.port, self.bind_interface)
|
||||
)
|
||||
# If using the network ID, delete any spurious router ID that might
|
||||
# have been in the request, same for network ID when using router ID.
|
||||
# This is to prevent someone from spoofing a metadata request using
|
||||
# the proxy via an external network. See LP #1865036 for more info.
|
||||
# This only applies to the non-OVN driver.
|
||||
if self.network_id:
|
||||
cfg_info['res_type'] = 'Network'
|
||||
cfg_info['res_id'] = self.network_id
|
||||
cfg_info['res_type_del'] = 'Router'
|
||||
else:
|
||||
cfg_info['res_type'] = 'Router'
|
||||
cfg_info['res_id'] = self.router_id
|
||||
cfg_info['res_type_del'] = 'Network'
|
||||
|
||||
haproxy_cfg = comm_meta.get_haproxy_config(cfg_info,
|
||||
self.rate_limiting_config,
|
||||
self.HEADER_CONFIG_TEMPLATE,
|
||||
_UNLIMITED_CONFIG_TEMPLATE)
|
||||
|
||||
LOG.debug("haproxy_cfg = %s", haproxy_cfg)
|
||||
cfg_dir = self.get_config_path(self.state_path)
|
||||
# uuid has to be included somewhere in the command line so that it can
|
||||
# be tracked by process_monitor.
|
||||
self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id'])
|
||||
if not os.path.exists(cfg_dir):
|
||||
os.makedirs(cfg_dir)
|
||||
with open(self.cfg_path, "w") as cfg_file:
|
||||
cfg_file.write(haproxy_cfg)
|
||||
|
||||
@classmethod
|
||||
def get_config_path(cls, state_path):
|
||||
return os.path.join(state_path or cfg.CONF.state_path,
|
||||
cls.PROXY_CONFIG_DIR)
|
||||
|
||||
@classmethod
|
||||
def cleanup_config_file(cls, uuid, state_path):
|
||||
"""Delete config file created when metadata proxy was spawned."""
|
||||
# Delete config file if it exists
|
||||
cfg_path = os.path.join(
|
||||
cls.get_config_path(state_path),
|
||||
"%s.conf" % uuid)
|
||||
linux_utils.delete_if_exists(cfg_path, run_as_root=True)
|
||||
|
||||
|
||||
class MetadataDriverBase(object, metaclass=abc.ABCMeta):
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def haproxy_configurator():
|
||||
"""Returns the HaproxyConfigurator for the class."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_user_group(cls, conf):
|
||||
user = conf.metadata_proxy_user or str(os.geteuid())
|
||||
group = conf.metadata_proxy_group or str(os.getegid())
|
||||
|
||||
return user, group
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_callback(cls, bind_address, port, conf,
|
||||
network_id=None, router_id=None,
|
||||
bind_address_v6=None,
|
||||
bind_interface=None):
|
||||
def callback(pid_file):
|
||||
metadata_proxy_socket = conf.metadata_proxy_socket
|
||||
user, group = cls._get_metadata_proxy_user_group(conf)
|
||||
configurator = cls.haproxy_configurator()
|
||||
haproxy = configurator(network_id,
|
||||
router_id,
|
||||
metadata_proxy_socket,
|
||||
bind_address,
|
||||
port,
|
||||
user,
|
||||
group,
|
||||
conf.state_path,
|
||||
pid_file,
|
||||
conf.metadata_rate_limiting,
|
||||
bind_address_v6,
|
||||
bind_interface)
|
||||
haproxy.create_config_file()
|
||||
proxy_cmd = [HAPROXY_SERVICE, '-f', haproxy.cfg_path]
|
||||
|
||||
return proxy_cmd
|
||||
|
||||
return callback
|
||||
|
||||
@classmethod
|
||||
def spawn_monitored_metadata_proxy(cls, monitor, ns_name, port, conf,
|
||||
bind_address="0.0.0.0", network_id=None,
|
||||
router_id=None, bind_address_v6=None,
|
||||
bind_interface=None):
|
||||
if bind_interface is not None and bind_address_v6 is not None:
|
||||
# HAProxy cannot bind() until IPv6 Duplicate Address Detection
|
||||
# completes. We must wait until the address leaves its 'tentative'
|
||||
# state.
|
||||
try:
|
||||
ip_lib.IpAddrCommand(
|
||||
parent=ip_lib.IPDevice(name=bind_interface,
|
||||
namespace=ns_name)
|
||||
).wait_until_address_ready(address=bind_address_v6)
|
||||
except ip_lib.DADFailed as exc:
|
||||
# This failure means that another DHCP agent has already
|
||||
# configured this metadata address, so all requests will
|
||||
# be via that single agent.
|
||||
LOG.info('DAD failed for address %(address)s on interface '
|
||||
'%(interface)s in namespace %(namespace)s on network '
|
||||
'%(network)s, deleting it. Exception: %(exception)s',
|
||||
{'address': bind_address_v6,
|
||||
'interface': bind_interface,
|
||||
'namespace': ns_name,
|
||||
'network': network_id,
|
||||
'exception': str(exc)})
|
||||
try:
|
||||
ip_lib.delete_ip_address(bind_address_v6, bind_interface,
|
||||
namespace=ns_name)
|
||||
except Exception as exc:
|
||||
# do not re-raise a delete failure, just log
|
||||
LOG.info('Address deletion failure: %s', str(exc))
|
||||
|
||||
# Do not use the address or interface when DAD fails
|
||||
bind_address_v6 = bind_interface = None
|
||||
|
||||
uuid = network_id or router_id
|
||||
callback = cls._get_metadata_proxy_callback(
|
||||
bind_address, port, conf,
|
||||
network_id=network_id, router_id=router_id,
|
||||
bind_address_v6=bind_address_v6, bind_interface=bind_interface)
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name,
|
||||
callback=callback)
|
||||
try:
|
||||
pm.enable(ensure_active=True)
|
||||
except exceptions.ProcessExecutionError as exec_err:
|
||||
LOG.error("Encountered process execution error %(err)s while "
|
||||
"starting process in namespace %(ns)s",
|
||||
{"err": exec_err, "ns": ns_name})
|
||||
return
|
||||
monitor.register(uuid, METADATA_SERVICE_NAME, pm)
|
||||
|
||||
@classmethod
|
||||
def destroy_monitored_metadata_proxy(cls, monitor, uuid, conf, ns_name):
|
||||
monitor.unregister(uuid, METADATA_SERVICE_NAME)
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name)
|
||||
pm.disable(sig=str(int(signal.SIGTERM)))
|
||||
try:
|
||||
common_utils.wait_until_true(lambda: not pm.active,
|
||||
timeout=SIGTERM_TIMEOUT)
|
||||
except common_utils.WaitTimeout:
|
||||
LOG.warning('Metadata process %s did not finish after SIGTERM '
|
||||
'signal in %s seconds, sending SIGKILL signal',
|
||||
pm.pid, SIGTERM_TIMEOUT)
|
||||
pm.disable(sig=str(int(signal.SIGKILL)))
|
||||
|
||||
# Delete metadata proxy config.
|
||||
configurator = cls.haproxy_configurator()
|
||||
configurator.cleanup_config_file(uuid, cfg.CONF.state_path)
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_process_manager(cls, router_id, conf, ns_name=None,
|
||||
callback=None):
|
||||
return external_process.ProcessManager(
|
||||
conf=conf,
|
||||
uuid=router_id,
|
||||
namespace=ns_name,
|
||||
service=HAPROXY_SERVICE,
|
||||
default_cmd_callback=callback)
|
|
@ -13,235 +13,19 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import errno
|
||||
import grp
|
||||
import os
|
||||
import pwd
|
||||
|
||||
from neutron.agent.linux import external_process
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron_lib import exceptions
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.common import metadata as comm_meta
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
METADATA_SERVICE_NAME = 'metadata-proxy'
|
||||
HAPROXY_SERVICE = 'haproxy'
|
||||
|
||||
PROXY_CONFIG_DIR = "ovn-metadata-proxy"
|
||||
from neutron.agent.metadata import driver_base
|
||||
|
||||
_HEADER_CONFIG_TEMPLATE = """
|
||||
http-request add-header X-OVN-%(res_type)s-ID %(res_id)s
|
||||
"""
|
||||
|
||||
_UNLIMITED_CONFIG_TEMPLATE = """
|
||||
listen listener
|
||||
bind %(host)s:%(port)s
|
||||
%(bind_v6_line)s
|
||||
server metadata %(unix_socket_path)s
|
||||
"""
|
||||
|
||||
class HaproxyConfigurator(driver_base.HaproxyConfiguratorBase):
|
||||
PROXY_CONFIG_DIR = "ovn-metadata-proxy"
|
||||
HEADER_CONFIG_TEMPLATE = _HEADER_CONFIG_TEMPLATE
|
||||
|
||||
|
||||
class HaproxyConfigurator(object):
|
||||
def __init__(self, network_id, router_id, unix_socket_path, host,
|
||||
port, user, group, state_path, pid_file,
|
||||
rate_limiting_config, host_v6=None,
|
||||
bind_interface=None):
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
if network_id is None and router_id is None:
|
||||
raise exceptions.NetworkIdOrRouterIdRequiredError()
|
||||
|
||||
self.host = host
|
||||
self.host_v6 = host_v6
|
||||
self.bind_interface = bind_interface
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.group = group
|
||||
self.state_path = state_path
|
||||
self.unix_socket_path = unix_socket_path
|
||||
self.pidfile = pid_file
|
||||
self.rate_limiting_config = rate_limiting_config
|
||||
self.log_level = (
|
||||
'debug' if logging.is_debug_enabled(cfg.CONF) else 'info')
|
||||
# log-tag will cause entries to have the string pre-pended, so use
|
||||
# the uuid haproxy will be started with. Additionally, if it
|
||||
# starts with "haproxy" then things will get logged to
|
||||
# /var/log/haproxy.log on Debian distros, instead of to syslog.
|
||||
uuid = network_id or router_id
|
||||
self.log_tag = 'haproxy-{}-{}'.format(METADATA_SERVICE_NAME, uuid)
|
||||
|
||||
def create_config_file(self):
|
||||
"""Create the config file for haproxy."""
|
||||
# Need to convert uid/gid into username/group
|
||||
try:
|
||||
username = pwd.getpwuid(int(self.user)).pw_name
|
||||
except (ValueError, KeyError):
|
||||
try:
|
||||
username = pwd.getpwnam(self.user).pw_name
|
||||
except KeyError:
|
||||
raise comm_meta.InvalidUserOrGroupException(
|
||||
_("Invalid user/uid: '%s'") % self.user)
|
||||
|
||||
try:
|
||||
groupname = grp.getgrgid(int(self.group)).gr_name
|
||||
except (ValueError, KeyError):
|
||||
try:
|
||||
groupname = grp.getgrnam(self.group).gr_name
|
||||
except KeyError:
|
||||
raise comm_meta.InvalidUserOrGroupException(
|
||||
_("Invalid group/gid: '%s'") % self.group)
|
||||
|
||||
cfg_info = {
|
||||
'host': self.host,
|
||||
'port': self.port,
|
||||
'unix_socket_path': self.unix_socket_path,
|
||||
'user': username,
|
||||
'group': groupname,
|
||||
'pidfile': self.pidfile,
|
||||
'log_level': self.log_level,
|
||||
'log_tag': self.log_tag,
|
||||
'bind_v6_line': '',
|
||||
}
|
||||
if self.host_v6 and self.bind_interface:
|
||||
cfg_info['bind_v6_line'] = (
|
||||
'bind %s:%s interface %s' % (
|
||||
self.host_v6, self.port, self.bind_interface)
|
||||
)
|
||||
if self.network_id:
|
||||
cfg_info['res_type'] = 'Network'
|
||||
cfg_info['res_id'] = self.network_id
|
||||
else:
|
||||
cfg_info['res_type'] = 'Router'
|
||||
cfg_info['res_id'] = self.router_id
|
||||
|
||||
haproxy_cfg = comm_meta.get_haproxy_config(cfg_info,
|
||||
self.rate_limiting_config,
|
||||
_HEADER_CONFIG_TEMPLATE,
|
||||
_UNLIMITED_CONFIG_TEMPLATE)
|
||||
|
||||
LOG.debug("haproxy_cfg = %s", haproxy_cfg)
|
||||
cfg_dir = self.get_config_path(self.state_path)
|
||||
# uuid has to be included somewhere in the command line so that it can
|
||||
# be tracked by process_monitor.
|
||||
self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id'])
|
||||
if not os.path.exists(cfg_dir):
|
||||
os.makedirs(cfg_dir)
|
||||
with open(self.cfg_path, "w") as cfg_file:
|
||||
cfg_file.write(haproxy_cfg)
|
||||
|
||||
class MetadataDriver(driver_base.MetadataDriverBase):
|
||||
@staticmethod
|
||||
def get_config_path(state_path):
|
||||
return os.path.join(state_path or cfg.CONF.state_path,
|
||||
PROXY_CONFIG_DIR)
|
||||
|
||||
@staticmethod
|
||||
def cleanup_config_file(uuid, state_path):
|
||||
"""Delete config file created when metadata proxy was spawned."""
|
||||
# Delete config file if it exists
|
||||
cfg_path = os.path.join(
|
||||
HaproxyConfigurator.get_config_path(state_path),
|
||||
"%s.conf" % uuid)
|
||||
try:
|
||||
os.unlink(cfg_path)
|
||||
except OSError as ex:
|
||||
# It can happen that this function is called but metadata proxy
|
||||
# was never spawned so its config file won't exist
|
||||
if ex.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
|
||||
class MetadataDriver(object):
|
||||
|
||||
monitors = {}
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_user_group(cls, conf):
|
||||
user = conf.metadata_proxy_user or str(os.geteuid())
|
||||
group = conf.metadata_proxy_group or str(os.getegid())
|
||||
|
||||
return user, group
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_callback(cls, bind_address, port, conf,
|
||||
network_id=None, router_id=None,
|
||||
bind_address_v6=None,
|
||||
bind_interface=None):
|
||||
def callback(pid_file):
|
||||
metadata_proxy_socket = conf.metadata_proxy_socket
|
||||
user, group = (
|
||||
cls._get_metadata_proxy_user_group(conf))
|
||||
haproxy = HaproxyConfigurator(network_id,
|
||||
router_id,
|
||||
metadata_proxy_socket,
|
||||
bind_address,
|
||||
port,
|
||||
user,
|
||||
group,
|
||||
conf.state_path,
|
||||
pid_file,
|
||||
conf.metadata_rate_limiting,
|
||||
bind_address_v6,
|
||||
bind_interface)
|
||||
haproxy.create_config_file()
|
||||
proxy_cmd = [HAPROXY_SERVICE,
|
||||
'-f', haproxy.cfg_path]
|
||||
return proxy_cmd
|
||||
|
||||
return callback
|
||||
|
||||
@classmethod
|
||||
def spawn_monitored_metadata_proxy(cls, monitor, ns_name, port, conf,
|
||||
bind_address="0.0.0.0", network_id=None,
|
||||
router_id=None, bind_address_v6=None,
|
||||
bind_interface=None):
|
||||
uuid = network_id or router_id
|
||||
callback = cls._get_metadata_proxy_callback(
|
||||
bind_address, port, conf, network_id=network_id,
|
||||
router_id=router_id, bind_address_v6=bind_address_v6,
|
||||
bind_interface=bind_interface)
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name,
|
||||
callback=callback)
|
||||
if bind_interface is not None and bind_address_v6 is not None:
|
||||
# HAProxy cannot bind() until IPv6 Duplicate Address Detection
|
||||
# completes. We must wait until the address leaves its 'tentative'
|
||||
# state.
|
||||
ip_lib.IpAddrCommand(
|
||||
parent=ip_lib.IPDevice(name=bind_interface, namespace=ns_name)
|
||||
).wait_until_address_ready(address=bind_address_v6)
|
||||
try:
|
||||
pm.enable(ensure_active=True)
|
||||
except exceptions.ProcessExecutionError as exec_err:
|
||||
LOG.error("Encountered process execution error %(err)s while "
|
||||
"starting process in namespace %(ns)s",
|
||||
{"err": exec_err, "ns": ns_name})
|
||||
return
|
||||
monitor.register(uuid, METADATA_SERVICE_NAME, pm)
|
||||
cls.monitors[router_id] = pm
|
||||
|
||||
@classmethod
|
||||
def destroy_monitored_metadata_proxy(cls, monitor, uuid, conf, ns_name):
|
||||
monitor.unregister(uuid, METADATA_SERVICE_NAME)
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name)
|
||||
pm.disable()
|
||||
|
||||
# Delete metadata proxy config file
|
||||
HaproxyConfigurator.cleanup_config_file(uuid, cfg.CONF.state_path)
|
||||
|
||||
cls.monitors.pop(uuid, None)
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_process_manager(cls, router_id, conf, ns_name=None,
|
||||
callback=None):
|
||||
return external_process.ProcessManager(
|
||||
conf=conf,
|
||||
uuid=router_id,
|
||||
namespace=ns_name,
|
||||
service=HAPROXY_SERVICE,
|
||||
default_cmd_callback=callback)
|
||||
def haproxy_configurator():
|
||||
return HaproxyConfigurator
|
||||
|
|
|
@ -34,7 +34,7 @@ from neutron.agent import l3_agent as l3_agent_main
|
|||
from neutron.agent.linux import external_process
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import keepalived
|
||||
from neutron.agent.metadata import driver as metadata_driver
|
||||
from neutron.agent.metadata import driver_base
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron.conf.agent import common as agent_config
|
||||
from neutron.conf.agent.l3 import config as l3_config
|
||||
|
@ -443,7 +443,7 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
|||
conf,
|
||||
router.router_id,
|
||||
router.ns_name,
|
||||
service=metadata_driver.HAPROXY_SERVICE)
|
||||
service=driver_base.HAPROXY_SERVICE)
|
||||
|
||||
def _metadata_proxy_exists(self, conf, router):
|
||||
pm = self._metadata_proxy(conf, router)
|
||||
|
|
|
@ -34,7 +34,7 @@ from neutron.agent.linux import external_process
|
|||
from neutron.agent.linux import interface
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import utils
|
||||
from neutron.agent.metadata import driver as metadata_driver
|
||||
from neutron.agent.metadata import driver_base
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron.conf.agent import common as config
|
||||
from neutron.tests.common import net_helpers
|
||||
|
@ -272,7 +272,7 @@ class DHCPAgentOVSTestFramework(base.BaseSudoTestCase):
|
|||
self.conf,
|
||||
network.id,
|
||||
network.namespace,
|
||||
service=metadata_driver.HAPROXY_SERVICE)
|
||||
service=driver_base.HAPROXY_SERVICE)
|
||||
|
||||
|
||||
class DHCPAgentOVSTestCase(DHCPAgentOVSTestFramework):
|
||||
|
|
|
@ -30,6 +30,7 @@ from neutron.agent.linux import ip_lib
|
|||
from neutron.agent.linux import iptables_manager
|
||||
from neutron.agent.linux import utils as linux_utils
|
||||
from neutron.agent.metadata import driver as metadata_driver
|
||||
from neutron.agent.metadata import driver_base
|
||||
from neutron.common import metadata as comm_meta
|
||||
from neutron.conf.agent import common as agent_config
|
||||
from neutron.conf.agent.l3 import config as l3_config
|
||||
|
@ -54,14 +55,14 @@ class TestMetadataDriverRules(base.BaseTestCase):
|
|||
'-p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697')
|
||||
self.assertEqual(
|
||||
[rules],
|
||||
metadata_driver.MetadataDriver.metadata_nat_rules(9697))
|
||||
metadata_driver.metadata_nat_rules(9697))
|
||||
|
||||
def test_metadata_nat_rules_ipv6(self):
|
||||
rules = ('PREROUTING', '-d fe80::a9fe:a9fe/128 -i qr-+ '
|
||||
'-p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697')
|
||||
self.assertEqual(
|
||||
[rules],
|
||||
metadata_driver.MetadataDriver.metadata_nat_rules(
|
||||
metadata_driver.metadata_nat_rules(
|
||||
9697, metadata_address='fe80::a9fe:a9fe/128'))
|
||||
|
||||
def test_metadata_filter_rules(self):
|
||||
|
@ -70,7 +71,7 @@ class TestMetadataDriverRules(base.BaseTestCase):
|
|||
('INPUT', '-p tcp -m tcp --dport 9697 -j DROP')]
|
||||
self.assertEqual(
|
||||
rules,
|
||||
metadata_driver.MetadataDriver.metadata_filter_rules(9697, '0x1'))
|
||||
metadata_driver.metadata_filter_rules(9697, '0x1'))
|
||||
|
||||
|
||||
class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
|
@ -216,7 +217,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
service_name,
|
||||
'-f', cfg_file]
|
||||
|
||||
log_tag = ("haproxy-" + metadata_driver.METADATA_SERVICE_NAME +
|
||||
log_tag = ("haproxy-" + driver_base.METADATA_SERVICE_NAME +
|
||||
"-" + router_id)
|
||||
|
||||
expected_params = {
|
||||
|
@ -251,7 +252,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
else:
|
||||
expected_config_template = (
|
||||
comm_meta.METADATA_HAPROXY_GLOBAL +
|
||||
metadata_driver._UNLIMITED_CONFIG_TEMPLATE +
|
||||
driver_base._UNLIMITED_CONFIG_TEMPLATE +
|
||||
metadata_driver._HEADER_CONFIG_TEMPLATE)
|
||||
|
||||
mock_open.assert_has_calls([
|
||||
|
@ -267,7 +268,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
])
|
||||
|
||||
agent.process_monitor.register.assert_called_once_with(
|
||||
router_id, metadata_driver.METADATA_SERVICE_NAME,
|
||||
router_id, driver_base.METADATA_SERVICE_NAME,
|
||||
mock.ANY)
|
||||
|
||||
self.delete_if_exists.assert_called_once_with(
|
||||
|
@ -293,7 +294,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
def test_spawn_metadata_proxy_dad_failed(self):
|
||||
self._test_spawn_metadata_proxy(dad_failed=True)
|
||||
|
||||
@mock.patch.object(metadata_driver.LOG, 'error')
|
||||
@mock.patch.object(driver_base.LOG, 'error')
|
||||
def test_spawn_metadata_proxy_handles_process_exception(self, error_log):
|
||||
process_instance = mock.Mock(active=False)
|
||||
process_instance.enable.side_effect = (
|
||||
|
@ -311,7 +312,6 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
network_id=network_id)
|
||||
error_log.assert_called_once()
|
||||
process_monitor.register.assert_not_called()
|
||||
self.assertNotIn(network_id, metadata_driver.MetadataDriver.monitors)
|
||||
|
||||
def test_create_config_file_wrong_user(self):
|
||||
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
||||
|
@ -356,7 +356,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
mock_get_process = self.mock_get_process.start()
|
||||
mock_get_process.return_value = mproxy_process
|
||||
driver = metadata_driver.MetadataDriver(FakeL3NATAgent())
|
||||
with mock.patch.object(metadata_driver, 'SIGTERM_TIMEOUT', 0):
|
||||
with mock.patch.object(driver_base, 'SIGTERM_TIMEOUT', 0):
|
||||
driver.destroy_monitored_metadata_proxy(mock.Mock(), 'uuid',
|
||||
'conf', 'ns_name')
|
||||
mproxy_process.disable.assert_has_calls([
|
||||
|
|
|
@ -26,6 +26,7 @@ from neutron.agent.linux.ip_lib import IpAddrCommand as ip_addr
|
|||
from neutron.agent.linux.ip_lib import IpLinkCommand as ip_link
|
||||
from neutron.agent.linux.ip_lib import IpNetnsCommand as ip_netns
|
||||
from neutron.agent.linux.ip_lib import IPWrapper as ip_wrap
|
||||
from neutron.agent.linux import utils as linux_utils
|
||||
from neutron.agent.ovn.metadata import agent
|
||||
from neutron.agent.ovn.metadata import driver
|
||||
from neutron.common.ovn import constants as ovn_const
|
||||
|
@ -451,6 +452,8 @@ class TestMetadataAgent(base.BaseTestCase):
|
|||
ip_wrap, 'add_veth',
|
||||
return_value=[ip_lib.IPDevice('ip1'),
|
||||
ip_lib.IPDevice('ip2')]) as add_veth,\
|
||||
mock.patch.object(
|
||||
linux_utils, 'delete_if_exists') as mock_delete,\
|
||||
mock.patch.object(
|
||||
driver.MetadataDriver,
|
||||
'spawn_monitored_metadata_proxy') as spawn_mdp, \
|
||||
|
@ -486,6 +489,7 @@ class TestMetadataAgent(base.BaseTestCase):
|
|||
self.assertCountEqual(expected_call,
|
||||
ip_addr_add_multiple.call_args.args[0])
|
||||
# Check that metadata proxy has been spawned
|
||||
mock_delete.assert_called_once_with(mock.ANY, run_as_root=True)
|
||||
spawn_mdp.assert_called_once_with(
|
||||
mock.ANY, nemaspace_name, 80, mock.ANY,
|
||||
bind_address=n_const.METADATA_V4_IP, network_id=net_name,
|
||||
|
|
|
@ -23,6 +23,7 @@ from oslo_utils import uuidutils
|
|||
|
||||
from neutron.agent.linux import external_process as ep
|
||||
from neutron.agent.linux import utils as linux_utils
|
||||
from neutron.agent.metadata import driver_base
|
||||
from neutron.agent.ovn.metadata import agent as metadata_agent
|
||||
from neutron.agent.ovn.metadata import driver as metadata_driver
|
||||
from neutron.common import metadata as comm_meta
|
||||
|
@ -40,6 +41,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
EUNAME = 'neutron'
|
||||
EGNAME = 'neutron'
|
||||
METADATA_DEFAULT_IP = '169.254.169.254'
|
||||
METADATA_DEFAULT_IPV6 = 'fe80::a9fe:a9fe'
|
||||
METADATA_PORT = 8080
|
||||
METADATA_SOCKET = '/socket/path'
|
||||
PIDFILE = 'pidfile'
|
||||
|
@ -99,27 +101,36 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
'neutron.agent.linux.external_process.'
|
||||
'ProcessManager.active',
|
||||
new_callable=mock.PropertyMock,
|
||||
side_effect=[False, True]):
|
||||
side_effect=[False, True]),\
|
||||
mock.patch(
|
||||
'neutron.agent.linux.ip_lib.'
|
||||
'IpAddrCommand.wait_until_address_ready',
|
||||
return_value=True):
|
||||
cfg_file = os.path.join(
|
||||
metadata_driver.HaproxyConfigurator.get_config_path(
|
||||
cfg.CONF.state_path),
|
||||
agent.conf.state_path),
|
||||
"%s.conf" % datapath_id)
|
||||
mock_open = self.useFixture(
|
||||
lib_fixtures.OpenFixture(cfg_file)).mock_open
|
||||
metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy(
|
||||
bind_v6_line = 'bind %s:%s interface %s' % (
|
||||
self.METADATA_DEFAULT_IPV6, self.METADATA_PORT, 'fake-if')
|
||||
proxy = metadata_driver.MetadataDriver()
|
||||
proxy.spawn_monitored_metadata_proxy(
|
||||
agent._process_monitor,
|
||||
metadata_ns,
|
||||
self.METADATA_PORT,
|
||||
cfg.CONF,
|
||||
agent.conf,
|
||||
bind_address=self.METADATA_DEFAULT_IP,
|
||||
network_id=datapath_id)
|
||||
network_id=datapath_id,
|
||||
bind_address_v6=self.METADATA_DEFAULT_IPV6,
|
||||
bind_interface='fake-if')
|
||||
|
||||
netns_execute_args = [
|
||||
service_name,
|
||||
'-f', cfg_file]
|
||||
|
||||
log_tag = '{}-{}-{}'.format(
|
||||
service_name, metadata_driver.METADATA_SERVICE_NAME,
|
||||
service_name, driver_base.METADATA_SERVICE_NAME,
|
||||
datapath_id)
|
||||
|
||||
expected_params = {
|
||||
|
@ -133,7 +144,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
'pidfile': self.PIDFILE,
|
||||
'log_level': 'debug',
|
||||
'log_tag': log_tag,
|
||||
'bind_v6_line': ''}
|
||||
'bind_v6_line': bind_v6_line}
|
||||
|
||||
if rate_limited:
|
||||
expected_params.update(self.RATE_LIMIT_CONFIG,
|
||||
|
@ -146,7 +157,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
else:
|
||||
expected_config_template = (
|
||||
comm_meta.METADATA_HAPROXY_GLOBAL +
|
||||
metadata_driver._UNLIMITED_CONFIG_TEMPLATE +
|
||||
driver_base._UNLIMITED_CONFIG_TEMPLATE +
|
||||
metadata_driver._HEADER_CONFIG_TEMPLATE)
|
||||
|
||||
cfg_contents = expected_config_template % expected_params
|
||||
|
@ -165,7 +176,7 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
self.delete_if_exists.assert_called_once_with(
|
||||
mock.ANY, run_as_root=True)
|
||||
|
||||
@mock.patch.object(metadata_driver.LOG, 'error')
|
||||
@mock.patch.object(driver_base.LOG, 'error')
|
||||
def test_spawn_metadata_proxy_handles_process_exception(self, error_log):
|
||||
process_instance = mock.Mock(active=False)
|
||||
process_instance.enable.side_effect = (
|
||||
|
@ -186,7 +197,6 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
|||
|
||||
error_log.assert_called_once()
|
||||
process_monitor.register.assert_not_called()
|
||||
self.assertNotIn(network_id, metadata_driver.MetadataDriver.monitors)
|
||||
|
||||
def test_create_config_file_wrong_user(self):
|
||||
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
||||
|
|
Loading…
Reference in New Issue