summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2023-05-15 17:37:10 +0000
committerGerrit Code Review <review@openstack.org>2023-05-15 17:37:10 +0000
commit03b4409500d9c1ae0d68f897ccd717bbc7e8ba5e (patch)
tree3862a26c7b246c6fc2060a49c365058525d36aaa
parentbeabb5193821a8427d8c5707f184c384bb848968 (diff)
parentdde1d69c78fdada1d4441914e685f8a202f68896 (diff)
downloadneutron-03b4409500d9c1ae0d68f897ccd717bbc7e8ba5e.tar.gz
Merge "Add host metadata haproxy manager"
-rw-r--r--neutron/agent/l2/extensions/metadata/__init__.py0
-rw-r--r--neutron/agent/l2/extensions/metadata/host_metadata_proxy.py200
-rw-r--r--neutron/conf/plugins/ml2/drivers/ovs_conf.py2
-rw-r--r--neutron/opts.py5
-rw-r--r--neutron/tests/unit/agent/l2/extensions/metadata/__init__.py0
-rw-r--r--neutron/tests/unit/agent/l2/extensions/metadata/test_host_metadata_proxy.py104
6 files changed, 310 insertions, 1 deletions
diff --git a/neutron/agent/l2/extensions/metadata/__init__.py b/neutron/agent/l2/extensions/metadata/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/neutron/agent/l2/extensions/metadata/__init__.py
diff --git a/neutron/agent/l2/extensions/metadata/host_metadata_proxy.py b/neutron/agent/l2/extensions/metadata/host_metadata_proxy.py
new file mode 100644
index 0000000000..aa89782b85
--- /dev/null
+++ b/neutron/agent/l2/extensions/metadata/host_metadata_proxy.py
@@ -0,0 +1,200 @@
+# Copyright (c) 2022 China Unicom Cloud Data Co.,Ltd.
+# 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 grp
+import io
+import os
+import pwd
+
+import jinja2
+from neutron_lib.utils import file as file_utils
+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 utils
+from neutron.common import metadata as common_meta
+from neutron.common import utils as common_utils
+
+LOG = logging.getLogger(__name__)
+
+PROXY_SERVICE_NAME = common_meta.PROXY_SERVICE_NAME
+PROXY_SERVICE_CMD = common_meta.PROXY_SERVICE_CMD
+
+_HOST_PATH_PROXY_TEMPLATE = jinja2.Template("""
+global
+ log /dev/log local0 {{ log_level }}
+ log-tag {{ log_tag }}
+ user {{ user }}
+ group {{ group }}
+ maxconn {{ maxconn }}
+ daemon
+
+frontend public
+ bind *:80 name clear
+ mode http
+ log global
+ option httplog
+ option dontlognull
+ maxconn {{ maxconn }}
+ timeout http-request 30s
+ timeout connect 30s
+ timeout client 32s
+ timeout server 32s
+ timeout http-keep-alive 30s
+
+ monitor-uri /monitoruri
+ stats uri /admin/stats
+{% for instance in instance_list %}
+ acl instance_{{ instance.uuid }}_{{ instance.provider_ip
+ }} src {{ instance.provider_ip }}
+{% endfor %}
+
+{% for instance in instance_list %}
+ use_backend backend_{{ instance.uuid }}_{{
+ instance.provider_ip }} if instance_{{ instance.uuid }}_{{
+ instance.provider_ip }}
+{% endfor %}
+
+{% for instance in instance_list %}
+backend backend_{{ instance.uuid }}_{{ instance.provider_ip }}
+ mode http
+ balance roundrobin
+ retries 3
+ option redispatch
+ timeout http-request 30s
+ timeout connect 30s
+ timeout server 30s
+
+ http-request set-header X-Instance-ID {{ instance.uuid }}
+ http-request set-header X-Tenant-ID {{ instance.project_id }}
+ http-request set-header X-Instance-ID-Signature {{ instance.signature }}
+
+ server metasrv {{ meta_api }}
+
+{% endfor %}
+""")
+
+
+class ProxyInstance(object):
+ def __init__(self, instance_id, provider_ip, project_id):
+ self.uuid = instance_id
+ self.provider_ip = provider_ip
+ self.project_id = project_id
+ self.signature = common_utils.sign_instance_id(
+ cfg.CONF.METADATA, self.uuid)
+
+
+class HostMedataHAProxyDaemonMonitor(object):
+ """Manage the data and state of a host metadata haproxy process."""
+
+ def __init__(self, process_monitor, uuid=None,
+ user=None, group=None):
+ self._host_id = uuid or "host_metadata_proxy"
+ self._process_monitor = process_monitor
+ self.haproxy_conf = None
+ self.user = user or str(os.geteuid())
+ self.group = group or str(os.getegid())
+
+ def _generate_proxy_conf(self, instance_infos):
+ haproxy_conf = utils.get_conf_file_name(
+ cfg.CONF.state_path, self._host_id,
+ 'haproxy.conf', True)
+ buf = io.StringIO()
+ meta_api = "%s:%s" % (
+ cfg.CONF.METADATA.nova_metadata_host,
+ cfg.CONF.METADATA.nova_metadata_port)
+
+ try:
+ username = pwd.getpwuid(int(self.user)).pw_name
+ except (ValueError, KeyError):
+ try:
+ username = pwd.getpwnam(self.user).pw_name
+ except KeyError:
+ raise common_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 common_meta.InvalidUserOrGroupException(
+ _("Invalid group/gid: '%s'") % self.group)
+
+ buf.write('%s' % _HOST_PATH_PROXY_TEMPLATE.render(
+ log_level='debug',
+ log_tag="%s-%s" % (PROXY_SERVICE_NAME, self._host_id),
+ user=username,
+ group=groupname,
+ maxconn=1024,
+ instance_list=instance_infos,
+ meta_api=meta_api))
+
+ contents = buf.getvalue()
+ LOG.debug("Host metadata haproxy config = %s", contents)
+ file_utils.replace_file(haproxy_conf, contents)
+ return haproxy_conf
+
+ def _get_proxy_process_manager(self, callback=None):
+ return external_process.ProcessManager(
+ conf=cfg.CONF,
+ uuid=self._host_id,
+ service=PROXY_SERVICE_NAME,
+ default_cmd_callback=callback,
+ run_as_root=True)
+
+ def _spawn_proxy(self, haproxy_conf):
+ def callback(pid_file):
+ proxy_cmd = [PROXY_SERVICE_NAME, '-f', '%s' % haproxy_conf,
+ '-p', '%s' % pid_file]
+ return proxy_cmd
+
+ pm = self._get_proxy_process_manager(callback)
+ pm.enable(reload_cfg=True)
+ self._process_monitor.register(uuid=self._host_id,
+ service_name=PROXY_SERVICE_NAME,
+ monitored_process=pm)
+ LOG.debug("Host metadata proxy enabled for host %s", self._host_id)
+
+ def config(self, instance_infos):
+ infos = []
+ for info in instance_infos:
+ infos.append(ProxyInstance(info['instance_id'],
+ info['provider_ip'],
+ info['project_id']))
+ if infos:
+ self.haproxy_conf = self._generate_proxy_conf(infos)
+
+ def enable(self):
+ if self.haproxy_conf:
+ self._spawn_proxy(self.haproxy_conf)
+ return
+
+ self.disable()
+
+ def disable(self):
+ self._process_monitor.unregister(uuid=self._host_id,
+ service_name=PROXY_SERVICE_NAME)
+ pm = self._get_proxy_process_manager()
+ pm.disable()
+ utils.remove_conf_files(cfg.CONF.state_path, self._host_id)
+ LOG.debug("Host metadata proxy disabled for host %s", self._host_id)
+
+ @property
+ def enabled(self):
+ return self._get_proxy_process_manager().active
diff --git a/neutron/conf/plugins/ml2/drivers/ovs_conf.py b/neutron/conf/plugins/ml2/drivers/ovs_conf.py
index 7bd43dc52d..e895e0098b 100644
--- a/neutron/conf/plugins/ml2/drivers/ovs_conf.py
+++ b/neutron/conf/plugins/ml2/drivers/ovs_conf.py
@@ -18,6 +18,7 @@ from oslo_config import cfg
from neutron._i18n import _
from neutron.conf.agent import common
+from neutron.conf.agent.metadata import config as meta_conf
DEFAULT_BRIDGE_MAPPINGS = []
@@ -260,6 +261,7 @@ def register_ovs_agent_opts(cfg=cfg.CONF):
cfg.register_opts(dhcp_opts, "DHCP")
cfg.register_opts(common.DHCP_PROTOCOL_OPTS, "DHCP")
cfg.register_opts(local_ip_opts, "LOCAL_IP")
+ cfg.register_opts(meta_conf.METADATA_PROXY_HANDLER_OPTS, "METADATA")
def register_ovs_opts(cfg=cfg.CONF):
diff --git a/neutron/opts.py b/neutron/opts.py
index b6117c9cc7..528126e71e 100644
--- a/neutron/opts.py
+++ b/neutron/opts.py
@@ -355,7 +355,10 @@ def list_ovs_opts():
('dhcp',
itertools.chain(
neutron.conf.plugins.ml2.drivers.ovs_conf.dhcp_opts,
- neutron.conf.agent.common.DHCP_PROTOCOL_OPTS))
+ neutron.conf.agent.common.DHCP_PROTOCOL_OPTS)),
+ ('metadata',
+ itertools.chain(
+ meta_conf.METADATA_PROXY_HANDLER_OPTS))
]
diff --git a/neutron/tests/unit/agent/l2/extensions/metadata/__init__.py b/neutron/tests/unit/agent/l2/extensions/metadata/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/neutron/tests/unit/agent/l2/extensions/metadata/__init__.py
diff --git a/neutron/tests/unit/agent/l2/extensions/metadata/test_host_metadata_proxy.py b/neutron/tests/unit/agent/l2/extensions/metadata/test_host_metadata_proxy.py
new file mode 100644
index 0000000000..c6a57d9846
--- /dev/null
+++ b/neutron/tests/unit/agent/l2/extensions/metadata/test_host_metadata_proxy.py
@@ -0,0 +1,104 @@
+# Copyright (c) 2022 China Unicom Cloud Data Co.,Ltd.
+# 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.
+
+from unittest import mock
+
+from oslo_config import cfg
+
+from neutron.agent.l2.extensions.metadata import host_metadata_proxy
+from neutron.agent.linux import external_process
+from neutron.tests import base
+
+
+class TestHostMedataHAProxyDaemonMonitor(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestHostMedataHAProxyDaemonMonitor, self).setUp()
+
+ self.ensure_dir = mock.patch(
+ 'oslo_utils.fileutils.ensure_tree').start()
+
+ self.utils_exec_p = mock.patch(
+ 'neutron.agent.linux.utils.execute')
+ self.utils_exec = self.utils_exec_p.start()
+
+ self.utils_replace_file_p = mock.patch(
+ 'neutron_lib.utils.file.replace_file')
+ self.utils_replace_file = self.utils_replace_file_p.start()
+
+ def test_spawn_host_metadata_haproxy(self):
+ cfg.CONF.set_override('metadata_proxy_shared_secret',
+ 'secret', group='METADATA')
+ conffile = '/fake/host_metadata_proxy.haproxy.conf'
+ pidfile = '/fake/host_metadata_proxy.pid.haproxy'
+ process_monitor = external_process.ProcessMonitor(
+ config=cfg.CONF,
+ resource_type='MetadataPath')
+
+ get_conf_file_name = 'neutron.agent.linux.utils.get_conf_file_name'
+ get_pid_file_name = ('neutron.agent.linux.external_process.'
+ 'ProcessManager.get_pid_file_name')
+ utils_execute = 'neutron.agent.common.utils.execute'
+
+ mock.patch(get_conf_file_name).start().return_value = conffile
+ mock.patch(get_pid_file_name).start().return_value = pidfile
+ execute = mock.patch(utils_execute).start()
+
+ host_meta = host_metadata_proxy.HostMedataHAProxyDaemonMonitor(
+ process_monitor)
+ instance_infos = [
+ {"instance_id": "uuid1",
+ "provider_ip": "1.1.1.1",
+ "project_id": "project1"}]
+ host_meta.config(instance_infos)
+ host_meta.enable()
+ cmd = execute.call_args[0][0]
+ _join = lambda *args: ' '.join(args)
+ cmd = _join(*cmd)
+ self.assertIn('haproxy', cmd)
+ self.assertIn(_join('-f', conffile), cmd)
+ self.assertIn(_join('-p', pidfile), cmd)
+
+ def test_generate_host_metadata_haproxy_config(self):
+ cfg.CONF.set_override('metadata_proxy_shared_secret',
+ 'secret', group='METADATA')
+ sig = (
+ "3b5421875d7ba0fc910202f5ce448d9419597e7b66f702b53335116fee60e81e")
+ cfg.CONF.set_override('nova_metadata_host',
+ '2.2.2.2',
+ group='METADATA')
+ cfg.CONF.set_override('nova_metadata_port',
+ '8775',
+ group='METADATA')
+ process_monitor = external_process.ProcessMonitor(
+ config=cfg.CONF,
+ resource_type='MetadataPath')
+ host_meta = host_metadata_proxy.HostMedataHAProxyDaemonMonitor(
+ process_monitor)
+ instance_infos = [
+ host_metadata_proxy.ProxyInstance('uuid1', '1.1.1.1', 'project1')]
+ host_meta._generate_proxy_conf(instance_infos)
+ acl = "acl instance_uuid1_1.1.1.1 src 1.1.1.1"
+ use_acl = "use_backend backend_uuid1_1.1.1.1 if instance_uuid1_1.1.1.1"
+ backend = "backend backend_uuid1_1.1.1.1"
+ http_hd_ins_id = "http-request set-header X-Instance-ID uuid1"
+ http_hd_pj = "http-request set-header X-Tenant-ID project1"
+ http_hd_sig = (
+ "http-request set-header X-Instance-ID-Signature %s" % sig)
+ meta_real_srv = "server metasrv 2.2.2.2:8775"
+ expects = [acl, use_acl, backend, http_hd_ins_id, http_hd_pj,
+ http_hd_sig, meta_real_srv]
+ for exp in expects:
+ self.assertIn(exp, self.utils_replace_file.call_args[0][1])