Merge "Switch ns-metadata-proxy to haproxy"
This commit is contained in:
commit
26b8848a9e
@ -8,6 +8,7 @@ gettext [test]
|
||||
# OpenStack infra that need these like
|
||||
# periodic-neutron-py27-with-oslo-master and
|
||||
# periodic-neutron-py35-with-neutron-lib-master.
|
||||
haproxy
|
||||
libmysqlclient-dev [platform:dpkg test]
|
||||
mysql [platform:rpm test]
|
||||
mysql-client [platform:dpkg test]
|
||||
|
@ -22,9 +22,13 @@ mm-ctl: CommandFilter, mm-ctl, root
|
||||
dhcp_release: CommandFilter, dhcp_release, root
|
||||
dhcp_release6: CommandFilter, dhcp_release6, root
|
||||
|
||||
# metadata proxy
|
||||
metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root
|
||||
# haproxy
|
||||
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
|
||||
kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP
|
||||
# RHEL invocation of the metadata proxy will report /usr/bin/python
|
||||
# TODO(dalvarez): Remove kill_metadata* filters in Q release since
|
||||
# neutron-ns-metadata-proxy is now replaced by haproxy. We keep them for now
|
||||
# for the migration process
|
||||
kill_metadata: KillFilter, root, python, -9
|
||||
kill_metadata7: KillFilter, root, python2.7, -9
|
||||
kill_metadata35: KillFilter, root, python3.5, -9
|
||||
|
@ -16,9 +16,13 @@ sysctl: CommandFilter, sysctl, root
|
||||
route: CommandFilter, route, root
|
||||
radvd: CommandFilter, radvd, root
|
||||
|
||||
# metadata proxy
|
||||
metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root
|
||||
# haproxy
|
||||
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
|
||||
kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP
|
||||
# RHEL invocation of the metadata proxy will report /usr/bin/python
|
||||
# TODO(dalvarez): Remove kill_metadata* filters in Q release since
|
||||
# neutron-ns-metadata-proxy is now replaced by haproxy. We keep them for now
|
||||
# for the migration process
|
||||
kill_metadata: KillFilter, root, python, -15, -9
|
||||
kill_metadata7: KillFilter, root, python2.7, -15, -9
|
||||
kill_metadata35: KillFilter, root, python3.5, -15, -9
|
||||
|
@ -122,8 +122,6 @@ def get_log_args(conf, log_file_name, **kwargs):
|
||||
log_dir = os.path.dirname(conf.log_file)
|
||||
if log_dir:
|
||||
cmd_args.append('--log-dir=%s' % log_dir)
|
||||
if kwargs.get('metadata_proxy_watch_log') is False:
|
||||
cmd_args.append('--nometadata_proxy_watch_log')
|
||||
else:
|
||||
if conf.use_syslog:
|
||||
cmd_args.append('--use-syslog')
|
||||
|
@ -33,7 +33,6 @@ def register_options(conf):
|
||||
config.register_agent_state_opts_helper(conf)
|
||||
config.register_availability_zone_opts_helper(conf)
|
||||
dhcp_config.register_agent_dhcp_opts(conf)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.DRIVER_OPTS, conf)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.SHARED_OPTS, conf)
|
||||
conf.register_opts(interface.OPTS)
|
||||
|
||||
|
@ -35,7 +35,6 @@ from neutron import service as neutron_service
|
||||
def register_opts(conf):
|
||||
l3_config.register_l3_agent_config_opts(l3_config.OPTS, conf)
|
||||
ha_conf.register_l3_agent_ha_opts(conf)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.DRIVER_OPTS, conf)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.SHARED_OPTS, conf)
|
||||
config.register_interface_driver_opts_helper(conf)
|
||||
config.register_agent_state_opts_helper(conf)
|
||||
|
@ -138,16 +138,21 @@ class ProcessManager(MonitoredProcess):
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
cmdline = self.cmdline
|
||||
return self.uuid in cmdline if cmdline else False
|
||||
|
||||
@property
|
||||
def cmdline(self):
|
||||
pid = self.pid
|
||||
if pid is None:
|
||||
return False
|
||||
if not pid:
|
||||
return
|
||||
|
||||
cmdline = '/proc/%s/cmdline' % pid
|
||||
try:
|
||||
with open(cmdline, "r") as f:
|
||||
return self.uuid in f.readline()
|
||||
return f.readline()
|
||||
except IOError:
|
||||
return False
|
||||
return
|
||||
|
||||
|
||||
ServiceId = collections.namedtuple('ServiceId', ['uuid', 'service'])
|
||||
|
@ -13,22 +13,146 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import errno
|
||||
import grp
|
||||
import os
|
||||
import pwd
|
||||
|
||||
from neutron.agent.common import config
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
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 utils
|
||||
from neutron.callbacks import events
|
||||
from neutron.callbacks import registry
|
||||
from neutron.callbacks import resources
|
||||
from neutron.common import constants
|
||||
from neutron.common import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
METADATA_SERVICE_NAME = 'metadata-proxy'
|
||||
|
||||
PROXY_CONFIG_DIR = "ns-metadata-proxy"
|
||||
_HAPROXY_CONFIG_TEMPLATE = """
|
||||
global
|
||||
log /dev/log local0 %(log_level)s
|
||||
user %(user)s
|
||||
group %(group)s
|
||||
maxconn 1024
|
||||
pidfile %(pidfile)s
|
||||
daemon
|
||||
|
||||
defaults
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
option http-server-close
|
||||
option forwardfor
|
||||
retries 3
|
||||
timeout http-request 30s
|
||||
timeout connect 30s
|
||||
timeout client 32s
|
||||
timeout server 32s
|
||||
timeout http-keep-alive 30s
|
||||
|
||||
listen listener
|
||||
bind 0.0.0.0:%(port)s
|
||||
server metadata %(unix_socket_path)s
|
||||
http-request add-header X-Neutron-%(res_type)s-ID %(res_id)s
|
||||
"""
|
||||
|
||||
|
||||
class InvalidUserOrGroupException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HaproxyConfigurator(object):
|
||||
def __init__(self, network_id, router_id, unix_socket_path, port, user,
|
||||
group, state_path, pid_file):
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
if network_id is None and router_id is None:
|
||||
raise exceptions.NetworkIdOrRouterIdRequiredError()
|
||||
|
||||
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.log_level = 'debug' if cfg.CONF.debug else 'info'
|
||||
|
||||
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 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 InvalidUserOrGroupException(
|
||||
_("Invalid group/gid: '%s'") % self.group)
|
||||
|
||||
cfg_info = {
|
||||
'port': self.port,
|
||||
'unix_socket_path': self.unix_socket_path,
|
||||
'user': username,
|
||||
'group': groupname,
|
||||
'pidfile': self.pidfile,
|
||||
'log_level': self.log_level
|
||||
}
|
||||
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 = _HAPROXY_CONFIG_TEMPLATE % cfg_info
|
||||
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)
|
||||
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):
|
||||
|
||||
@ -71,45 +195,30 @@ class MetadataDriver(object):
|
||||
'port': port})]
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_user_group_watchlog(cls, conf):
|
||||
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())
|
||||
|
||||
watch_log = conf.metadata_proxy_watch_log
|
||||
if watch_log is None:
|
||||
# NOTE(cbrandily): Commonly, log watching can be enabled only
|
||||
# when metadata proxy user is agent effective user (id/name).
|
||||
watch_log = utils.is_effective_user(user)
|
||||
|
||||
return user, group, watch_log
|
||||
return user, group
|
||||
|
||||
@classmethod
|
||||
def _get_metadata_proxy_callback(cls, port, conf, network_id=None,
|
||||
router_id=None):
|
||||
uuid = network_id or router_id
|
||||
if uuid is None:
|
||||
raise exceptions.NetworkIdOrRouterIdRequiredError()
|
||||
|
||||
if network_id:
|
||||
lookup_param = '--network_id=%s' % network_id
|
||||
else:
|
||||
lookup_param = '--router_id=%s' % router_id
|
||||
|
||||
def callback(pid_file):
|
||||
metadata_proxy_socket = conf.metadata_proxy_socket
|
||||
user, group, watch_log = (
|
||||
cls._get_metadata_proxy_user_group_watchlog(conf))
|
||||
proxy_cmd = ['neutron-ns-metadata-proxy',
|
||||
'--pid_file=%s' % pid_file,
|
||||
'--metadata_proxy_socket=%s' % metadata_proxy_socket,
|
||||
lookup_param,
|
||||
'--state_path=%s' % conf.state_path,
|
||||
'--metadata_port=%s' % port,
|
||||
'--metadata_proxy_user=%s' % user,
|
||||
'--metadata_proxy_group=%s' % group]
|
||||
proxy_cmd.extend(config.get_log_args(
|
||||
conf, 'neutron-ns-metadata-proxy-%s.log' % uuid,
|
||||
metadata_proxy_watch_log=watch_log))
|
||||
user, group = (
|
||||
cls._get_metadata_proxy_user_group(conf))
|
||||
haproxy = HaproxyConfigurator(network_id,
|
||||
router_id,
|
||||
metadata_proxy_socket,
|
||||
port,
|
||||
user,
|
||||
group,
|
||||
conf.state_path,
|
||||
pid_file)
|
||||
haproxy.create_config_file()
|
||||
proxy_cmd = ['haproxy',
|
||||
'-f', haproxy.cfg_path]
|
||||
return proxy_cmd
|
||||
|
||||
return callback
|
||||
@ -123,16 +232,41 @@ class MetadataDriver(object):
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf,
|
||||
ns_name=ns_name,
|
||||
callback=callback)
|
||||
# TODO(dalvarez): Remove in Q cycle. This will kill running instances
|
||||
# of old ns-metadata-proxy Python version in order to be replaced by
|
||||
# haproxy. This will help with upgrading and shall be removed in next
|
||||
# cycle.
|
||||
cls._migrate_python_ns_metadata_proxy_if_needed(pm)
|
||||
|
||||
pm.enable()
|
||||
monitor.register(uuid, METADATA_SERVICE_NAME, pm)
|
||||
cls.monitors[router_id] = pm
|
||||
|
||||
@staticmethod
|
||||
def _migrate_python_ns_metadata_proxy_if_needed(pm):
|
||||
"""Kill running Python version of ns-metadata-proxy.
|
||||
|
||||
This function will detect if the current metadata proxy process is
|
||||
running the old Python version and kill it so that the new haproxy
|
||||
version is spawned instead.
|
||||
"""
|
||||
# Read cmdline to a local var to avoid reading twice from /proc file
|
||||
cmdline = pm.cmdline
|
||||
if cmdline and 'haproxy' not in cmdline:
|
||||
LOG.debug("Migrating old instance of python ns-metadata proxy to "
|
||||
"new one based on haproxy (%s)", cmdline)
|
||||
pm.disable()
|
||||
|
||||
@classmethod
|
||||
def destroy_monitored_metadata_proxy(cls, monitor, uuid, conf):
|
||||
monitor.unregister(uuid, METADATA_SERVICE_NAME)
|
||||
# No need to pass ns name as it's not needed for disable()
|
||||
pm = cls._get_metadata_proxy_process_manager(uuid, conf)
|
||||
pm.disable()
|
||||
|
||||
# Delete metadata proxy config file
|
||||
HaproxyConfigurator.cleanup_config_file(uuid, cfg.CONF.state_path)
|
||||
|
||||
cls.monitors.pop(uuid, None)
|
||||
|
||||
@classmethod
|
||||
|
@ -1,159 +0,0 @@
|
||||
# Copyright 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 httplib2
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import wsgi as base_wsgi
|
||||
from oslo_utils import encodeutils
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import webob
|
||||
|
||||
from neutron._i18n import _, _LE
|
||||
from neutron.agent.linux import daemon
|
||||
from neutron.agent.linux import utils as agent_utils
|
||||
from neutron.common import config
|
||||
from neutron.common import exceptions
|
||||
from neutron.common import utils
|
||||
from neutron.conf.agent.metadata import namespace_proxy as namespace
|
||||
from neutron import wsgi
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkMetadataProxyHandler(object):
|
||||
"""Proxy AF_INET metadata request through Unix Domain socket.
|
||||
|
||||
The Unix domain socket allows the proxy access resource that are not
|
||||
accessible within the isolated tenant context.
|
||||
"""
|
||||
|
||||
def __init__(self, network_id=None, router_id=None):
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
|
||||
if network_id is None and router_id is None:
|
||||
raise exceptions.NetworkIdOrRouterIdRequiredError()
|
||||
|
||||
@webob.dec.wsgify(RequestClass=base_wsgi.Request)
|
||||
def __call__(self, req):
|
||||
LOG.debug("Request: %s", req)
|
||||
try:
|
||||
return self._proxy_request(req.remote_addr,
|
||||
req.method,
|
||||
req.path_info,
|
||||
req.query_string,
|
||||
req.body)
|
||||
except Exception:
|
||||
LOG.exception(_LE("Unexpected error."))
|
||||
msg = _('An unknown error has occurred. '
|
||||
'Please try your request again.')
|
||||
explanation = six.text_type(msg)
|
||||
return webob.exc.HTTPInternalServerError(explanation=explanation)
|
||||
|
||||
def _proxy_request(self, remote_address, method, path_info,
|
||||
query_string, body):
|
||||
headers = {
|
||||
'X-Forwarded-For': remote_address,
|
||||
}
|
||||
|
||||
if self.router_id:
|
||||
headers['X-Neutron-Router-ID'] = self.router_id
|
||||
else:
|
||||
headers['X-Neutron-Network-ID'] = self.network_id
|
||||
|
||||
url = urlparse.urlunsplit((
|
||||
'http',
|
||||
'169.254.169.254', # a dummy value to make the request proper
|
||||
path_info,
|
||||
query_string,
|
||||
''))
|
||||
|
||||
h = httplib2.Http()
|
||||
resp, content = h.request(
|
||||
url,
|
||||
method=method,
|
||||
headers=headers,
|
||||
body=body,
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection)
|
||||
|
||||
if resp.status == 200:
|
||||
LOG.debug(resp)
|
||||
LOG.debug(encodeutils.safe_decode(content, errors='replace'))
|
||||
response = webob.Response()
|
||||
response.status = resp.status
|
||||
response.headers['Content-Type'] = resp['content-type']
|
||||
response.body = wsgi.encode_body(content)
|
||||
return response
|
||||
elif resp.status == 400:
|
||||
return webob.exc.HTTPBadRequest()
|
||||
elif resp.status == 404:
|
||||
return webob.exc.HTTPNotFound()
|
||||
elif resp.status == 409:
|
||||
return webob.exc.HTTPConflict()
|
||||
elif resp.status == 500:
|
||||
msg = _(
|
||||
'Remote metadata server experienced an internal server error.'
|
||||
)
|
||||
LOG.debug(msg)
|
||||
explanation = six.text_type(msg)
|
||||
return webob.exc.HTTPInternalServerError(explanation=explanation)
|
||||
else:
|
||||
raise Exception(_('Unexpected response code: %s') % resp.status)
|
||||
|
||||
|
||||
class ProxyDaemon(daemon.Daemon):
|
||||
def __init__(self, pidfile, port, network_id=None, router_id=None,
|
||||
user=None, group=None, watch_log=True):
|
||||
uuid = network_id or router_id
|
||||
super(ProxyDaemon, self).__init__(pidfile, uuid=uuid, user=user,
|
||||
group=group, watch_log=watch_log)
|
||||
self.network_id = network_id
|
||||
self.router_id = router_id
|
||||
self.port = port
|
||||
|
||||
def run(self):
|
||||
handler = NetworkMetadataProxyHandler(
|
||||
self.network_id,
|
||||
self.router_id)
|
||||
proxy = wsgi.Server('neutron-network-metadata-proxy')
|
||||
proxy.start(handler, self.port)
|
||||
|
||||
# Drop privileges after port bind
|
||||
super(ProxyDaemon, self).run()
|
||||
|
||||
proxy.wait()
|
||||
|
||||
|
||||
def main():
|
||||
namespace.register_namespace_proxy_opts(cfg.CONF)
|
||||
# Don't read any default configuration file, just handle cmdline opts
|
||||
cfg.CONF(project='neutron',
|
||||
default_config_files=[], default_config_dirs=[])
|
||||
config.setup_logging()
|
||||
utils.log_opt_values(LOG)
|
||||
|
||||
proxy = ProxyDaemon(cfg.CONF.pid_file,
|
||||
cfg.CONF.metadata_port,
|
||||
network_id=cfg.CONF.network_id,
|
||||
router_id=cfg.CONF.router_id,
|
||||
user=cfg.CONF.metadata_proxy_user,
|
||||
group=cfg.CONF.metadata_proxy_group,
|
||||
watch_log=cfg.CONF.metadata_proxy_watch_log)
|
||||
|
||||
if cfg.CONF.daemonize:
|
||||
proxy.start()
|
||||
else:
|
||||
proxy.run()
|
@ -1,17 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from neutron.agent.metadata import namespace_proxy
|
||||
|
||||
|
||||
def main():
|
||||
namespace_proxy.main()
|
@ -40,20 +40,6 @@ SHARED_OPTS = [
|
||||
]
|
||||
|
||||
|
||||
DRIVER_OPTS = [
|
||||
cfg.BoolOpt('metadata_proxy_watch_log',
|
||||
help=_("Enable/Disable log watch by metadata proxy. It "
|
||||
"should be disabled when metadata_proxy_user/group "
|
||||
"is not allowed to read/write its log file and "
|
||||
"copytruncate logrotate option must be used if "
|
||||
"logrotate is enabled on metadata proxy log "
|
||||
"files. Option default value is deduced from "
|
||||
"metadata_proxy_user: watch log is enabled if "
|
||||
"metadata_proxy_user is agent effective user "
|
||||
"id/name.")),
|
||||
]
|
||||
|
||||
|
||||
METADATA_PROXY_HANDLER_OPTS = [
|
||||
cfg.StrOpt('auth_ca_cert',
|
||||
help=_("Certificate Authority public key (CA cert) "
|
||||
|
@ -1,54 +0,0 @@
|
||||
# Copyright 2016 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron._i18n import _
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt('network_id',
|
||||
help=_('Network that will have instance metadata '
|
||||
'proxied.')),
|
||||
cfg.StrOpt('router_id',
|
||||
help=_('Router that will have connected instances\' '
|
||||
'metadata proxied.')),
|
||||
cfg.StrOpt('pid_file',
|
||||
help=_('Location of pid file of this process.')),
|
||||
cfg.BoolOpt('daemonize',
|
||||
default=True,
|
||||
help=_('Run as daemon.')),
|
||||
cfg.PortOpt('metadata_port',
|
||||
default=9697,
|
||||
help=_('TCP Port to listen for metadata server'
|
||||
'requests.')),
|
||||
cfg.StrOpt('metadata_proxy_socket',
|
||||
default='$state_path/metadata_proxy',
|
||||
help=_('Location of Metadata Proxy UNIX domain '
|
||||
'socket')),
|
||||
cfg.StrOpt('metadata_proxy_user',
|
||||
help=_('User (uid or name) running metadata proxy after '
|
||||
'its initialization')),
|
||||
cfg.StrOpt('metadata_proxy_group',
|
||||
help=_('Group (gid or name) running metadata proxy after '
|
||||
'its initialization')),
|
||||
cfg.BoolOpt('metadata_proxy_watch_log',
|
||||
default=True,
|
||||
help=_('Watch file log. Log watch should be disabled when '
|
||||
'metadata_proxy_user/group has no read/write '
|
||||
'permissions on metadata proxy log file.')),
|
||||
]
|
||||
|
||||
|
||||
def register_namespace_proxy_opts(cfg=cfg.CONF):
|
||||
cfg.register_cli_opts(OPTS)
|
@ -89,8 +89,7 @@ def list_agent_opts():
|
||||
('DEFAULT',
|
||||
itertools.chain(
|
||||
neutron.agent.common.config.INTERFACE_DRIVER_OPTS,
|
||||
neutron.conf.agent.metadata.config.SHARED_OPTS,
|
||||
neutron.conf.agent.metadata.config.DRIVER_OPTS)
|
||||
neutron.conf.agent.metadata.config.SHARED_OPTS)
|
||||
)
|
||||
]
|
||||
|
||||
|
@ -83,8 +83,6 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
||||
get_temp_file_path = functools.partial(self.get_temp_file_path,
|
||||
root=temp_dir)
|
||||
conf.set_override('state_path', temp_dir.path)
|
||||
# NOTE(cbrandily): log_file or log_dir must be set otherwise
|
||||
# metadata_proxy_watch_log has no effect
|
||||
conf.set_override('log_file',
|
||||
get_temp_file_path('log_file'))
|
||||
conf.set_override('metadata_proxy_socket',
|
||||
|
@ -12,20 +12,24 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import os.path
|
||||
import time
|
||||
|
||||
import fixtures
|
||||
from oslo_config import cfg
|
||||
import webob
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from neutron.agent.linux import dhcp
|
||||
from neutron.agent.linux import external_process
|
||||
from neutron.agent.linux import utils
|
||||
from neutron.tests.common import machine_fixtures
|
||||
from neutron.tests.common import net_helpers
|
||||
from neutron.tests.functional.agent.l3 import framework
|
||||
from neutron.tests.functional.agent.linux import helpers
|
||||
|
||||
from neutron.tests.functional.agent.linux import simple_daemon
|
||||
|
||||
METADATA_REQUEST_TIMEOUT = 60
|
||||
METADATA_REQUEST_SLEEP = 5
|
||||
@ -118,6 +122,57 @@ class MetadataL3AgentTestCase(framework.L3AgentTestFramework):
|
||||
# Check status code
|
||||
self.assertIn(str(webob.exc.HTTPOk.code), firstline.split())
|
||||
|
||||
@staticmethod
|
||||
def _make_cmdline_callback(uuid):
|
||||
def _cmdline_callback(pidfile):
|
||||
cmdline = ["python", simple_daemon.__file__,
|
||||
"--uuid=%s" % uuid,
|
||||
"--pid_file=%s" % pidfile]
|
||||
return cmdline
|
||||
return _cmdline_callback
|
||||
|
||||
def test_haproxy_migration_path(self):
|
||||
"""Test the migration path for haproxy.
|
||||
|
||||
This test will launch the simple_daemon Python process before spawning
|
||||
haproxy. When launching haproxy, it will be detected and killed, as
|
||||
it's running on the same pidfile and with the router uuid in its
|
||||
cmdline.
|
||||
"""
|
||||
# Make sure that external_pids configuration option is the same for
|
||||
# simple_daemon and haproxy so that both work on the same pid_file.
|
||||
get_temp_file_path = functools.partial(
|
||||
self.get_temp_file_path,
|
||||
root=self.useFixture(fixtures.TempDir()))
|
||||
cfg.CONF.set_override('external_pids',
|
||||
get_temp_file_path('external/pids'))
|
||||
self.agent.conf.set_override('external_pids',
|
||||
get_temp_file_path('external/pids'))
|
||||
|
||||
router_info = self.generate_router_info(enable_ha=False)
|
||||
|
||||
# Spawn the simple_daemon process in the background using the generated
|
||||
# router uuid. We are not registering it within ProcessMonitor so that
|
||||
# it doesn't get respawned once killed.
|
||||
_callback = self._make_cmdline_callback(router_info['id'])
|
||||
pm = external_process.ProcessManager(
|
||||
conf=cfg.CONF,
|
||||
uuid=router_info['id'],
|
||||
default_cmd_callback=_callback)
|
||||
pm.enable()
|
||||
self.addCleanup(pm.disable)
|
||||
|
||||
# Make sure that simple_daemon is running
|
||||
self.assertIn('simple_daemon', pm.cmdline)
|
||||
|
||||
# Create the router. This is expected to launch haproxy after killing
|
||||
# the simple_daemon process.
|
||||
self.manage_router(self.agent, router_info)
|
||||
|
||||
# Make sure that it was killed and replaced by haproxy
|
||||
self.assertNotIn('simple_daemon', pm.cmdline)
|
||||
self.assertIn('haproxy', pm.cmdline)
|
||||
|
||||
|
||||
class UnprivilegedUserMetadataL3AgentTestCase(MetadataL3AgentTestCase):
|
||||
"""Test metadata proxy with least privileged user.
|
||||
@ -131,7 +186,6 @@ class UnprivilegedUserMetadataL3AgentTestCase(MetadataL3AgentTestCase):
|
||||
def setUp(self):
|
||||
super(UnprivilegedUserMetadataL3AgentTestCase, self).setUp()
|
||||
self.agent.conf.set_override('metadata_proxy_user', '65534')
|
||||
self.agent.conf.set_override('metadata_proxy_watch_log', False)
|
||||
|
||||
|
||||
class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase):
|
||||
@ -149,4 +203,3 @@ class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase):
|
||||
super(UnprivilegedUserGroupMetadataL3AgentTestCase, self).setUp()
|
||||
self.agent.conf.set_override('metadata_proxy_user', '65534')
|
||||
self.agent.conf.set_override('metadata_proxy_group', '65534')
|
||||
self.agent.conf.set_override('metadata_proxy_watch_log', False)
|
||||
|
@ -231,7 +231,9 @@ class TestDhcpAgent(base.BaseTestCase):
|
||||
self.driver_cls.return_value = self.driver
|
||||
self.mock_makedirs_p = mock.patch("os.makedirs")
|
||||
self.mock_makedirs = self.mock_makedirs_p.start()
|
||||
|
||||
self.mock_create_metadata_proxy_cfg = mock.patch(
|
||||
"neutron.agent.metadata.driver.HaproxyConfigurator")
|
||||
self.mock_create_metadata_proxy_cfg.start()
|
||||
self.mock_ip_wrapper_p = mock.patch("neutron.agent.linux.ip_lib."
|
||||
"IPWrapper")
|
||||
self.mock_ip_wrapper = self.mock_ip_wrapper_p.start()
|
||||
@ -673,7 +675,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
||||
if is_isolated_network and enable_isolated_metadata:
|
||||
self.external_process.assert_has_calls([
|
||||
self._process_manager_constructor_call(),
|
||||
mock.call().enable()])
|
||||
mock.call().enable()], any_order=True)
|
||||
else:
|
||||
self.external_process.assert_has_calls([
|
||||
self._process_manager_constructor_call(ns=None),
|
||||
@ -839,7 +841,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
||||
self.external_process.assert_has_calls([
|
||||
self._process_manager_constructor_call(),
|
||||
mock.call().enable()
|
||||
])
|
||||
], any_order=True)
|
||||
|
||||
def test_disable_isolated_metadata_proxy(self):
|
||||
method_path = ('neutron.agent.metadata.driver.MetadataDriver'
|
||||
|
@ -25,6 +25,7 @@ from neutron.tests import tools
|
||||
TEST_UUID = 'test-uuid'
|
||||
TEST_SERVICE = 'testsvc'
|
||||
TEST_PID = 1234
|
||||
TEST_CMDLINE = 'python foo --router_id=%s'
|
||||
|
||||
|
||||
class BaseTestProcessMonitor(base.BaseTestCase):
|
||||
@ -264,32 +265,42 @@ class TestProcessManager(base.BaseTestCase):
|
||||
self.assertIsNone(manager.pid)
|
||||
|
||||
def test_active(self):
|
||||
mock_open = self.useFixture(
|
||||
tools.OpenFixture('/proc/4/cmdline', 'python foo --router_id=uuid')
|
||||
).mock_open
|
||||
with mock.patch.object(ep.ProcessManager, 'pid') as pid:
|
||||
pid.__get__ = mock.Mock(return_value=4)
|
||||
with mock.patch.object(ep.ProcessManager, 'cmdline') as cmdline:
|
||||
cmdline.__get__ = mock.Mock(
|
||||
return_value=TEST_CMDLINE % 'uuid')
|
||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||
self.assertTrue(manager.active)
|
||||
|
||||
mock_open.assert_called_once_with('/proc/4/cmdline', 'r')
|
||||
|
||||
def test_active_none(self):
|
||||
dummy_cmd_line = 'python foo --router_id=uuid'
|
||||
self.execute.return_value = dummy_cmd_line
|
||||
with mock.patch.object(ep.ProcessManager, 'pid') as pid:
|
||||
pid.__get__ = mock.Mock(return_value=None)
|
||||
with mock.patch.object(ep.ProcessManager, 'cmdline') as cmdline:
|
||||
cmdline.__get__ = mock.Mock(return_value=None)
|
||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||
self.assertFalse(manager.active)
|
||||
|
||||
def test_active_cmd_mismatch(self):
|
||||
with mock.patch.object(ep.ProcessManager, 'cmdline') as cmdline:
|
||||
cmdline.__get__ = mock.Mock(
|
||||
return_value=TEST_CMDLINE % 'anotherid')
|
||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||
self.assertFalse(manager.active)
|
||||
|
||||
def test_cmdline(self):
|
||||
mock_open = self.useFixture(
|
||||
tools.OpenFixture('/proc/4/cmdline',
|
||||
'python foo --router_id=anotherid')
|
||||
tools.OpenFixture('/proc/4/cmdline', TEST_CMDLINE % 'uuid')
|
||||
).mock_open
|
||||
with mock.patch.object(ep.ProcessManager, 'pid') as pid:
|
||||
pid.__get__ = mock.Mock(return_value=4)
|
||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||
self.assertFalse(manager.active)
|
||||
|
||||
self.assertEqual(TEST_CMDLINE % 'uuid', manager.cmdline)
|
||||
mock_open.assert_called_once_with('/proc/4/cmdline', 'r')
|
||||
|
||||
def test_cmdline_none(self):
|
||||
mock_open = self.useFixture(
|
||||
tools.OpenFixture('/proc/4/cmdline', TEST_CMDLINE % 'uuid')
|
||||
).mock_open
|
||||
mock_open.side_effect = IOError()
|
||||
with mock.patch.object(ep.ProcessManager, 'pid') as pid:
|
||||
pid.__get__ = mock.Mock(return_value=4)
|
||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||
self.assertIsNone(manager.cmdline)
|
||||
mock_open.assert_called_once_with('/proc/4/cmdline', 'r')
|
||||
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
@ -26,7 +28,8 @@ from neutron.conf.agent.l3 import config as l3_config
|
||||
from neutron.conf.agent.l3 import ha as ha_conf
|
||||
from neutron.conf.agent.metadata import config as meta_conf
|
||||
from neutron.tests import base
|
||||
|
||||
from neutron.tests import tools
|
||||
from neutron.tests.unit.agent.linux import test_utils
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
@ -60,9 +63,11 @@ class TestMetadataDriverRules(base.BaseTestCase):
|
||||
|
||||
class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
|
||||
EUID = 123
|
||||
EGID = 456
|
||||
EUNAME = 'neutron'
|
||||
EGNAME = 'neutron'
|
||||
METADATA_PORT = 8080
|
||||
METADATA_SOCKET = '/socket/path'
|
||||
PIDFILE = 'pidfile'
|
||||
|
||||
def setUp(self):
|
||||
super(TestMetadataDriverProcess, self).setUp()
|
||||
@ -78,7 +83,6 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF)
|
||||
ha_conf.register_l3_agent_ha_opts()
|
||||
meta_conf.register_meta_conf_opts(meta_conf.SHARED_OPTS, cfg.CONF)
|
||||
meta_conf.register_meta_conf_opts(meta_conf.DRIVER_OPTS, cfg.CONF)
|
||||
|
||||
def test_after_router_updated_called_on_agent_process_update(self):
|
||||
with mock.patch.object(metadata_driver, 'after_router_updated') as f,\
|
||||
@ -93,74 +97,105 @@ class TestMetadataDriverProcess(base.BaseTestCase):
|
||||
f.assert_called_once_with(
|
||||
'router', 'after_update', agent, router=ri)
|
||||
|
||||
def _test_spawn_metadata_proxy(self, expected_user, expected_group,
|
||||
user='', group='', watch_log=True):
|
||||
def test_spawn_metadata_proxy(self):
|
||||
router_id = _uuid()
|
||||
router_ns = 'qrouter-%s' % router_id
|
||||
metadata_port = 8080
|
||||
ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'
|
||||
is_effective_user = 'neutron.agent.linux.utils.is_effective_user'
|
||||
fake_is_effective_user = lambda x: x in [self.EUNAME, str(self.EUID)]
|
||||
|
||||
cfg.CONF.set_override('metadata_proxy_user', user)
|
||||
cfg.CONF.set_override('metadata_proxy_group', group)
|
||||
cfg.CONF.set_override('log_file', 'test.log')
|
||||
cfg.CONF.set_override('metadata_proxy_user', self.EUNAME)
|
||||
cfg.CONF.set_override('metadata_proxy_group', self.EGNAME)
|
||||
cfg.CONF.set_override('metadata_proxy_socket', self.METADATA_SOCKET)
|
||||
cfg.CONF.set_override('debug', True)
|
||||
|
||||
agent = l3_agent.L3NATAgent('localhost')
|
||||
with mock.patch('os.geteuid', return_value=self.EUID),\
|
||||
mock.patch('os.getegid', return_value=self.EGID),\
|
||||
mock.patch(is_effective_user,
|
||||
side_effect=fake_is_effective_user),\
|
||||
mock.patch(ip_class_path) as ip_mock:
|
||||
with mock.patch(ip_class_path) as ip_mock,\
|
||||
mock.patch(
|
||||
'neutron.agent.linux.external_process.'
|
||||
'ProcessManager.get_pid_file_name',
|
||||
return_value=self.PIDFILE),\
|
||||
mock.patch('pwd.getpwnam',
|
||||
return_value=test_utils.FakeUser(self.EUNAME)),\
|
||||
mock.patch('grp.getgrnam',
|
||||
return_value=test_utils.FakeGroup(self.EGNAME)),\
|
||||
mock.patch('os.makedirs'):
|
||||
cfg_file = os.path.join(
|
||||
metadata_driver.HaproxyConfigurator.get_config_path(
|
||||
agent.conf.state_path),
|
||||
"%s.conf" % router_id)
|
||||
mock_open = self.useFixture(
|
||||
tools.OpenFixture(cfg_file)).mock_open
|
||||
agent.metadata_driver.spawn_monitored_metadata_proxy(
|
||||
agent.process_monitor,
|
||||
router_ns,
|
||||
metadata_port,
|
||||
self.METADATA_PORT,
|
||||
agent.conf,
|
||||
router_id=router_id)
|
||||
|
||||
netns_execute_args = [
|
||||
'neutron-ns-metadata-proxy',
|
||||
mock.ANY,
|
||||
mock.ANY,
|
||||
'--router_id=%s' % router_id,
|
||||
mock.ANY,
|
||||
'--metadata_port=%s' % metadata_port,
|
||||
'--metadata_proxy_user=%s' % expected_user,
|
||||
'--metadata_proxy_group=%s' % expected_group,
|
||||
'--debug',
|
||||
'--log-file=neutron-ns-metadata-proxy-%s.log' %
|
||||
router_id]
|
||||
if not watch_log:
|
||||
netns_execute_args.append(
|
||||
'--nometadata_proxy_watch_log')
|
||||
'haproxy',
|
||||
'-f', cfg_file]
|
||||
|
||||
cfg_contents = metadata_driver._HAPROXY_CONFIG_TEMPLATE % {
|
||||
'user': self.EUNAME,
|
||||
'group': self.EGNAME,
|
||||
'port': self.METADATA_PORT,
|
||||
'unix_socket_path': self.METADATA_SOCKET,
|
||||
'res_type': 'Router',
|
||||
'res_id': router_id,
|
||||
'pidfile': self.PIDFILE,
|
||||
'log_level': 'debug'}
|
||||
|
||||
mock_open.assert_has_calls([
|
||||
mock.call(cfg_file, 'w'),
|
||||
mock.call().write(cfg_contents)],
|
||||
any_order=True)
|
||||
|
||||
ip_mock.assert_has_calls([
|
||||
mock.call(namespace=router_ns),
|
||||
mock.call().netns.execute(netns_execute_args, addl_env=None,
|
||||
run_as_root=False)
|
||||
])
|
||||
|
||||
def test_spawn_metadata_proxy_with_agent_user(self):
|
||||
self._test_spawn_metadata_proxy(
|
||||
self.EUNAME, str(self.EGID), user=self.EUNAME)
|
||||
def test_create_config_file_wrong_user(self):
|
||||
with mock.patch('pwd.getpwnam', side_effect=KeyError):
|
||||
config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY,
|
||||
mock.ANY, mock.ANY,
|
||||
self.EUNAME,
|
||||
self.EGNAME,
|
||||
mock.ANY, mock.ANY)
|
||||
self.assertRaises(metadata_driver.InvalidUserOrGroupException,
|
||||
config.create_config_file)
|
||||
|
||||
def test_spawn_metadata_proxy_with_nonagent_user(self):
|
||||
self._test_spawn_metadata_proxy(
|
||||
'notneutron', str(self.EGID), user='notneutron', watch_log=False)
|
||||
def test_create_config_file_wrong_group(self):
|
||||
with mock.patch('grp.getgrnam', side_effect=KeyError),\
|
||||
mock.patch('pwd.getpwnam',
|
||||
return_value=test_utils.FakeUser(self.EUNAME)):
|
||||
config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY,
|
||||
mock.ANY, mock.ANY,
|
||||
self.EUNAME,
|
||||
self.EGNAME,
|
||||
mock.ANY, mock.ANY)
|
||||
self.assertRaises(metadata_driver.InvalidUserOrGroupException,
|
||||
config.create_config_file)
|
||||
|
||||
def test_spawn_metadata_proxy_with_agent_uid(self):
|
||||
self._test_spawn_metadata_proxy(
|
||||
str(self.EUID), str(self.EGID), user=str(self.EUID))
|
||||
def test__migrate_python_ns_metadata_proxy_if_needed(self):
|
||||
agent = l3_agent.L3NATAgent('localhost')
|
||||
with mock.patch(
|
||||
'neutron.agent.linux.external_process.ProcessManager')\
|
||||
as mock_pm:
|
||||
mock_pm.cmdline = (
|
||||
'python neutron-ns-metadata-proxy')
|
||||
(agent.metadata_driver
|
||||
._migrate_python_ns_metadata_proxy_if_needed(mock_pm))
|
||||
mock_pm.disable.assert_called_once_with()
|
||||
|
||||
def test_spawn_metadata_proxy_with_nonagent_uid(self):
|
||||
self._test_spawn_metadata_proxy(
|
||||
'321', str(self.EGID), user='321', watch_log=False)
|
||||
|
||||
def test_spawn_metadata_proxy_with_group(self):
|
||||
self._test_spawn_metadata_proxy(str(self.EUID), 'group', group='group')
|
||||
|
||||
def test_spawn_metadata_proxy_with_gid(self):
|
||||
self._test_spawn_metadata_proxy(str(self.EUID), '654', group='654')
|
||||
|
||||
def test_spawn_metadata_proxy(self):
|
||||
self._test_spawn_metadata_proxy(str(self.EUID), str(self.EGID))
|
||||
def test__migrate_python_ns_metadata_proxy_if_needed_not_called(self):
|
||||
agent = l3_agent.L3NATAgent('localhost')
|
||||
with mock.patch(
|
||||
'neutron.agent.linux.external_process.ProcessManager')\
|
||||
as mock_pm:
|
||||
mock_pm.cmdline = (
|
||||
'haproxy -f foo.cfg')
|
||||
(agent.metadata_driver
|
||||
._migrate_python_ns_metadata_proxy_if_needed(mock_pm))
|
||||
mock_pm.disable.assert_not_called()
|
||||
|
@ -1,313 +0,0 @@
|
||||
# Copyright 2012 New Dream Network, LLC (DreamHost)
|
||||
#
|
||||
# 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 mock
|
||||
import testtools
|
||||
import webob
|
||||
|
||||
from neutron.agent.linux import utils as agent_utils
|
||||
from neutron.agent.metadata import namespace_proxy as ns_proxy
|
||||
from neutron.common import exceptions
|
||||
from neutron.common import utils
|
||||
from neutron.tests import base
|
||||
from neutron import wsgi
|
||||
|
||||
|
||||
class TestNetworkMetadataProxyHandler(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestNetworkMetadataProxyHandler, self).setUp()
|
||||
self.handler = ns_proxy.NetworkMetadataProxyHandler('router_id')
|
||||
|
||||
def test_call(self):
|
||||
req = mock.Mock(headers={})
|
||||
with mock.patch.object(self.handler, '_proxy_request') as proxy_req:
|
||||
proxy_req.return_value = 'value'
|
||||
|
||||
retval = self.handler(req)
|
||||
self.assertEqual(retval, 'value')
|
||||
proxy_req.assert_called_once_with(req.remote_addr,
|
||||
req.method,
|
||||
req.path_info,
|
||||
req.query_string,
|
||||
req.body)
|
||||
|
||||
def test_no_argument_passed_to_init(self):
|
||||
with testtools.ExpectedException(
|
||||
exceptions.NetworkIdOrRouterIdRequiredError):
|
||||
ns_proxy.NetworkMetadataProxyHandler()
|
||||
|
||||
def test_call_internal_server_error(self):
|
||||
req = mock.Mock(headers={})
|
||||
with mock.patch.object(self.handler, '_proxy_request') as proxy_req:
|
||||
proxy_req.side_effect = Exception
|
||||
retval = self.handler(req)
|
||||
self.assertIsInstance(retval, webob.exc.HTTPInternalServerError)
|
||||
self.assertTrue(proxy_req.called)
|
||||
|
||||
def test_proxy_request_router_200(self):
|
||||
self.handler.router_id = 'router_id'
|
||||
|
||||
resp = mock.MagicMock(status=200)
|
||||
with mock.patch('httplib2.Http') as mock_http:
|
||||
resp.__getitem__.return_value = "text/plain"
|
||||
mock_http.return_value.request.return_value = (resp, 'content')
|
||||
|
||||
retval = self.handler._proxy_request('192.168.1.1',
|
||||
'GET',
|
||||
'/latest/meta-data',
|
||||
'',
|
||||
'')
|
||||
|
||||
mock_http.assert_has_calls([
|
||||
mock.call().request(
|
||||
'http://169.254.169.254/latest/meta-data',
|
||||
method='GET',
|
||||
headers={
|
||||
'X-Forwarded-For': '192.168.1.1',
|
||||
'X-Neutron-Router-ID': 'router_id'
|
||||
},
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection,
|
||||
body=''
|
||||
)]
|
||||
)
|
||||
|
||||
self.assertEqual(retval.headers['Content-Type'], 'text/plain')
|
||||
self.assertEqual(b'content', retval.body)
|
||||
|
||||
def _test_proxy_request_network_200(self, content):
|
||||
self.handler.network_id = 'network_id'
|
||||
|
||||
resp = mock.MagicMock(status=200)
|
||||
with mock.patch('httplib2.Http') as mock_http:
|
||||
resp.__getitem__.return_value = "application/json"
|
||||
mock_http.return_value.request.return_value = (resp, content)
|
||||
|
||||
retval = self.handler._proxy_request('192.168.1.1',
|
||||
'GET',
|
||||
'/latest/meta-data',
|
||||
'',
|
||||
'')
|
||||
|
||||
mock_http.assert_has_calls([
|
||||
mock.call().request(
|
||||
'http://169.254.169.254/latest/meta-data',
|
||||
method='GET',
|
||||
headers={
|
||||
'X-Forwarded-For': '192.168.1.1',
|
||||
'X-Neutron-Network-ID': 'network_id'
|
||||
},
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection,
|
||||
body=''
|
||||
)]
|
||||
)
|
||||
|
||||
self.assertEqual(retval.headers['Content-Type'],
|
||||
'application/json')
|
||||
self.assertEqual(wsgi.encode_body(content), retval.body)
|
||||
|
||||
def test_proxy_request_network_200(self):
|
||||
self._test_proxy_request_network_200('{}')
|
||||
|
||||
def test_proxy_request_network_200_unicode_in_content(self):
|
||||
self._test_proxy_request_network_200('Gl\xfcck')
|
||||
|
||||
def _test_proxy_request_network_4xx(self, status, method, expected):
|
||||
self.handler.network_id = 'network_id'
|
||||
|
||||
resp = mock.Mock(status=status)
|
||||
with mock.patch('httplib2.Http') as mock_http:
|
||||
mock_http.return_value.request.return_value = (resp, '')
|
||||
|
||||
retval = self.handler._proxy_request('192.168.1.1',
|
||||
method,
|
||||
'/latest/meta-data',
|
||||
'',
|
||||
'')
|
||||
|
||||
mock_http.assert_has_calls([
|
||||
mock.call().request(
|
||||
'http://169.254.169.254/latest/meta-data',
|
||||
method=method,
|
||||
headers={
|
||||
'X-Forwarded-For': '192.168.1.1',
|
||||
'X-Neutron-Network-ID': 'network_id'
|
||||
},
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection,
|
||||
body=''
|
||||
)]
|
||||
)
|
||||
|
||||
self.assertIsInstance(retval, expected)
|
||||
|
||||
def test_proxy_request_network_400(self):
|
||||
self._test_proxy_request_network_4xx(
|
||||
400, 'GET', webob.exc.HTTPBadRequest)
|
||||
|
||||
def test_proxy_request_network_404(self):
|
||||
self._test_proxy_request_network_4xx(
|
||||
404, 'GET', webob.exc.HTTPNotFound)
|
||||
|
||||
def test_proxy_request_network_409(self):
|
||||
self._test_proxy_request_network_4xx(
|
||||
409, 'POST', webob.exc.HTTPConflict)
|
||||
|
||||
def test_proxy_request_network_500(self):
|
||||
self.handler.network_id = 'network_id'
|
||||
|
||||
resp = mock.Mock(status=500)
|
||||
with mock.patch('httplib2.Http') as mock_http:
|
||||
mock_http.return_value.request.return_value = (resp, '')
|
||||
|
||||
retval = self.handler._proxy_request('192.168.1.1',
|
||||
'GET',
|
||||
'/latest/meta-data',
|
||||
'',
|
||||
'')
|
||||
|
||||
mock_http.assert_has_calls([
|
||||
mock.call().request(
|
||||
'http://169.254.169.254/latest/meta-data',
|
||||
method='GET',
|
||||
headers={
|
||||
'X-Forwarded-For': '192.168.1.1',
|
||||
'X-Neutron-Network-ID': 'network_id'
|
||||
},
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection,
|
||||
body=''
|
||||
)]
|
||||
)
|
||||
|
||||
self.assertIsInstance(retval, webob.exc.HTTPInternalServerError)
|
||||
|
||||
def test_proxy_request_network_418(self):
|
||||
self.handler.network_id = 'network_id'
|
||||
|
||||
resp = mock.Mock(status=418)
|
||||
with mock.patch('httplib2.Http') as mock_http:
|
||||
mock_http.return_value.request.return_value = (resp, '')
|
||||
|
||||
with testtools.ExpectedException(Exception):
|
||||
self.handler._proxy_request('192.168.1.1',
|
||||
'GET',
|
||||
'/latest/meta-data',
|
||||
'',
|
||||
'')
|
||||
|
||||
mock_http.assert_has_calls([
|
||||
mock.call().request(
|
||||
'http://169.254.169.254/latest/meta-data',
|
||||
method='GET',
|
||||
headers={
|
||||
'X-Forwarded-For': '192.168.1.1',
|
||||
'X-Neutron-Network-ID': 'network_id'
|
||||
},
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection,
|
||||
body=''
|
||||
)]
|
||||
)
|
||||
|
||||
def test_proxy_request_network_exception(self):
|
||||
self.handler.network_id = 'network_id'
|
||||
|
||||
mock.Mock(status=500)
|
||||
with mock.patch('httplib2.Http') as mock_http:
|
||||
mock_http.return_value.request.side_effect = Exception
|
||||
|
||||
with testtools.ExpectedException(Exception):
|
||||
self.handler._proxy_request('192.168.1.1',
|
||||
'GET',
|
||||
'/latest/meta-data',
|
||||
'',
|
||||
'')
|
||||
|
||||
mock_http.assert_has_calls([
|
||||
mock.call().request(
|
||||
'http://169.254.169.254/latest/meta-data',
|
||||
method='GET',
|
||||
headers={
|
||||
'X-Forwarded-For': '192.168.1.1',
|
||||
'X-Neutron-Network-ID': 'network_id'
|
||||
},
|
||||
connection_type=agent_utils.UnixDomainHTTPConnection,
|
||||
body=''
|
||||
)]
|
||||
)
|
||||
|
||||
|
||||
class TestProxyDaemon(base.BaseTestCase):
|
||||
def test_init(self):
|
||||
with mock.patch('neutron.agent.linux.daemon.Pidfile'):
|
||||
pd = ns_proxy.ProxyDaemon('pidfile', 9697, 'net_id', 'router_id')
|
||||
self.assertEqual(pd.router_id, 'router_id')
|
||||
self.assertEqual(pd.network_id, 'net_id')
|
||||
|
||||
def test_run(self):
|
||||
with mock.patch('neutron.agent.linux.daemon.Pidfile'):
|
||||
with mock.patch('neutron.wsgi.Server') as Server:
|
||||
pd = ns_proxy.ProxyDaemon('pidfile', 9697, 'net_id',
|
||||
'router_id')
|
||||
pd.run()
|
||||
Server.assert_has_calls([
|
||||
mock.call('neutron-network-metadata-proxy'),
|
||||
mock.call().start(mock.ANY, 9697),
|
||||
mock.call().wait()]
|
||||
)
|
||||
|
||||
def test_main(self):
|
||||
with mock.patch.object(ns_proxy, 'ProxyDaemon') as daemon:
|
||||
with mock.patch.object(ns_proxy, 'config') as config:
|
||||
with mock.patch.object(ns_proxy, 'cfg') as cfg:
|
||||
with mock.patch.object(utils, 'cfg') as utils_cfg:
|
||||
cfg.CONF.router_id = 'router_id'
|
||||
cfg.CONF.network_id = None
|
||||
cfg.CONF.metadata_port = 9697
|
||||
cfg.CONF.pid_file = 'pidfile'
|
||||
cfg.CONF.daemonize = True
|
||||
utils_cfg.CONF.log_opt_values.return_value = None
|
||||
ns_proxy.main()
|
||||
|
||||
self.assertTrue(config.setup_logging.called)
|
||||
daemon.assert_has_calls([
|
||||
mock.call('pidfile', 9697,
|
||||
router_id='router_id',
|
||||
network_id=None,
|
||||
user=mock.ANY,
|
||||
group=mock.ANY,
|
||||
watch_log=mock.ANY),
|
||||
mock.call().start()]
|
||||
)
|
||||
|
||||
def test_main_dont_fork(self):
|
||||
with mock.patch.object(ns_proxy, 'ProxyDaemon') as daemon:
|
||||
with mock.patch.object(ns_proxy, 'config') as config:
|
||||
with mock.patch.object(ns_proxy, 'cfg') as cfg:
|
||||
with mock.patch.object(utils, 'cfg') as utils_cfg:
|
||||
cfg.CONF.router_id = 'router_id'
|
||||
cfg.CONF.network_id = None
|
||||
cfg.CONF.metadata_port = 9697
|
||||
cfg.CONF.pid_file = 'pidfile'
|
||||
cfg.CONF.daemonize = False
|
||||
utils_cfg.CONF.log_opt_values.return_value = None
|
||||
ns_proxy.main()
|
||||
|
||||
self.assertTrue(config.setup_logging.called)
|
||||
daemon.assert_has_calls([
|
||||
mock.call('pidfile', 9697,
|
||||
router_id='router_id',
|
||||
network_id=None,
|
||||
user=mock.ANY,
|
||||
group=mock.ANY,
|
||||
watch_log=mock.ANY),
|
||||
mock.call().run()]
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
---
|
||||
features:
|
||||
- In order to reduce metadata proxy memory footprint, ``haproxy`` is now used
|
||||
as a replacement for ``neutron-ns-metadata-proxy`` Python implementation.
|
||||
upgrade:
|
||||
- Since ``haproxy`` was not used before by ``neutron-l3-agent`` and
|
||||
``neutron-dhcp-agent``, rootwrap filters for both agents have to be copied
|
||||
over when upgrading.
|
||||
- To upgrade to the ``haproxy`` based metadata proxy, ``neutron-l3-agent``
|
||||
and ``neutron-dhcp-agent`` have to be restarted. On startup, old proxy
|
||||
processes will be detected and replaced with ``haproxy``.
|
||||
|
@ -53,7 +53,6 @@ console_scripts =
|
||||
neutron-macvtap-agent = neutron.cmd.eventlet.plugins.macvtap_neutron_agent:main
|
||||
neutron-metadata-agent = neutron.cmd.eventlet.agents.metadata:main
|
||||
neutron-netns-cleanup = neutron.cmd.netns_cleanup:main
|
||||
neutron-ns-metadata-proxy = neutron.cmd.eventlet.agents.metadata_proxy:main
|
||||
neutron-openvswitch-agent = neutron.cmd.eventlet.plugins.ovs_neutron_agent:main
|
||||
neutron-ovs-cleanup = neutron.cmd.ovs_cleanup:main
|
||||
neutron-pd-notify = neutron.cmd.pd_notify:main
|
||||
|
Loading…
x
Reference in New Issue
Block a user