Add basical functionalities for metadata path extension
This patch adds an agent extension for Neutron openvswitch agent to make the metadata datapath distributed. The extension will do the following works when handle port: 1. allocate Meta_IP and Meta_MAC 2. setup the host haproxy configurations for this port 3. cache port infos for deleting The extension will do the following works when delete port: 1. remove haproxy configurations of this port 2. clean up cache infos Partially-Implements: blueprint distributed-metadata-datapath Change-Id: Ia6e3e57b7e2ff61e8e7c950c095df15ffa3abd7a
This commit is contained in:
@@ -44,7 +44,7 @@ global
|
||||
daemon
|
||||
|
||||
frontend public
|
||||
bind *:80 name clear
|
||||
bind *:{{ bind_port }} name clear
|
||||
mode http
|
||||
log global
|
||||
option httplog
|
||||
@@ -142,6 +142,7 @@ class HostMedataHAProxyDaemonMonitor:
|
||||
user=username,
|
||||
group=groupname,
|
||||
maxconn=1024,
|
||||
bind_port=cfg.CONF.METADATA.host_proxy_listen_port,
|
||||
instance_list=instance_infos,
|
||||
meta_api=meta_api))
|
||||
|
||||
|
318
neutron/agent/l2/extensions/metadata/metadata_path.py
Normal file
318
neutron/agent/l2/extensions/metadata/metadata_path.py
Normal file
@@ -0,0 +1,318 @@
|
||||
# Copyright (c) 2023 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 os
|
||||
import secrets
|
||||
import time
|
||||
|
||||
import netaddr
|
||||
from neutron_lib.agent import l2_extension as l2_agent_extension
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.plugins.ml2 import ovs_constants as p_const
|
||||
from neutron_lib.plugins import utils as p_utils
|
||||
from neutron_lib.utils import net as net_lib
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.agent.common import ip_lib
|
||||
from neutron.agent.l2.extensions.metadata import host_metadata_proxy
|
||||
from neutron.agent.linux import external_process
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_META_GATEWAY_MAC = "fa:16:ee:00:00:01"
|
||||
|
||||
|
||||
class InvalidProviderCIDR(n_exc.NeutronException):
|
||||
message = _("Not enough Metadata IPs in /32 CIDR")
|
||||
|
||||
|
||||
class NoMoreProviderRes(n_exc.NeutronException):
|
||||
message = _("No more %(res)s")
|
||||
|
||||
|
||||
class FailedToInitMetadataPathExtension(n_exc.NeutronException):
|
||||
message = _("Could not initialize agent extension "
|
||||
"metadata path, error: %(msg)s")
|
||||
|
||||
|
||||
class MetadataPathExtensionPortInfoAPI():
|
||||
|
||||
def __init__(self, cache_api):
|
||||
self.cache_api = cache_api
|
||||
self.allocated_ips = netaddr.IPSet()
|
||||
self.allocated_macs = set()
|
||||
|
||||
def get_port_fixed_ip(self, port):
|
||||
for ip in port.fixed_ips:
|
||||
ip_addr = netaddr.IPAddress(str(ip.ip_address))
|
||||
if ip_addr.version == constants.IP_VERSION_4:
|
||||
return str(ip.ip_address)
|
||||
|
||||
def remove_allocated_ip(self, ip):
|
||||
self.allocated_ips.remove(ip)
|
||||
|
||||
def remove_allocated_mac(self, mac):
|
||||
self.allocated_macs.remove(mac)
|
||||
|
||||
def _get_one_ip(self):
|
||||
|
||||
def generate_local_ip(cidr):
|
||||
network = netaddr.IPNetwork(cidr)
|
||||
if network.prefixlen == 32:
|
||||
raise InvalidProviderCIDR()
|
||||
# https://docs.python.org/3/library/secrets.html#module-secrets
|
||||
# secrets.randbelow(exclusive_upper_bound)
|
||||
# Return a random int in the range [0, exclusive_upper_bound).
|
||||
# Here we remove the first and last IPs here.
|
||||
index = secrets.randbelow(network.size - 1)
|
||||
return str(network[index + 1])
|
||||
|
||||
for _i in range(1, 100):
|
||||
ip = generate_local_ip(cfg.CONF.METADATA.provider_cidr)
|
||||
if ip not in self.allocated_ips:
|
||||
return ip
|
||||
raise NoMoreProviderRes(res="provider IP addresses")
|
||||
|
||||
def _get_one_mac(self):
|
||||
for _i in range(1, 1000):
|
||||
base_mac = cfg.CONF.METADATA.provider_base_mac
|
||||
mac = net_lib.get_random_mac(base_mac.split(':'))
|
||||
if mac not in self.allocated_macs:
|
||||
return mac
|
||||
raise NoMoreProviderRes(res="provider MAC addresses")
|
||||
|
||||
def get_provider_ip_info(self, port_id,
|
||||
provider_ip=None,
|
||||
provider_mac=None):
|
||||
port_obj = self.cache_api.get_resource_by_id(
|
||||
resources.PORT, port_id)
|
||||
if not port_obj or not port_obj.device_id:
|
||||
return
|
||||
|
||||
info = {"instance_id": port_obj.device_id,
|
||||
"project_id": port_obj.project_id}
|
||||
|
||||
if (not provider_ip or netaddr.IPNetwork(provider_ip) not in
|
||||
netaddr.IPNetwork(cfg.CONF.METADATA.provider_cidr)):
|
||||
provider_ip = self._get_one_ip()
|
||||
self.allocated_ips.add(provider_ip)
|
||||
info["provider_ip"] = provider_ip
|
||||
|
||||
if not provider_mac:
|
||||
provider_mac = self._get_one_mac()
|
||||
self.allocated_macs.add(provider_mac)
|
||||
info["provider_port_mac"] = provider_mac
|
||||
|
||||
return info
|
||||
|
||||
|
||||
class MetadataPathAgentExtension(l2_agent_extension.L2AgentExtension):
|
||||
|
||||
PORT_INFO_CACHE = {}
|
||||
META_DEV_NAME = "tap-meta"
|
||||
|
||||
@lockutils.synchronized('networking-path-ofport-cache')
|
||||
def set_port_info_cache(self, port_id, port_info):
|
||||
self.PORT_INFO_CACHE[port_id] = port_info
|
||||
|
||||
@lockutils.synchronized('networking-path-ofport-cache')
|
||||
def get_port_info_from_cache(self, port_id):
|
||||
return self.PORT_INFO_CACHE.pop(port_id, None)
|
||||
|
||||
def consume_api(self, agent_api):
|
||||
if not all([agent_api.br_phys.get('meta'), agent_api.phys_ofports,
|
||||
agent_api.bridge_mappings.get('meta')]):
|
||||
raise FailedToInitMetadataPathExtension(
|
||||
msg="The metadata bridge device may not exist.")
|
||||
|
||||
self.agent_api = agent_api
|
||||
self.rcache_api = agent_api.plugin_rpc.remote_resource_cache
|
||||
|
||||
def initialize(self, connection, driver_type):
|
||||
"""Initialize agent extension."""
|
||||
self.ext_api = MetadataPathExtensionPortInfoAPI(self.rcache_api)
|
||||
self.int_br = self.agent_api.request_int_br()
|
||||
self.meta_br = self.agent_api.request_physical_br('meta')
|
||||
self.instance_infos = {}
|
||||
|
||||
bridge = self.agent_api.bridge_mappings.get('meta')
|
||||
port_name = p_utils.get_interface_name(
|
||||
bridge, prefix=p_const.PEER_INTEGRATION_PREFIX)
|
||||
self.ofport_int_to_meta = self.int_br.get_port_ofport(port_name)
|
||||
self.ofport_meta_to_int = self.agent_api.phys_ofports['meta']
|
||||
|
||||
if (not cfg.CONF.METADATA.nova_metadata_host or
|
||||
not cfg.CONF.METADATA.nova_metadata_port):
|
||||
LOG.warning("Nova metadata API related options are not set. "
|
||||
"Host metadata haproxy will not start. "
|
||||
"Please check the config option of "
|
||||
"'nova_metadata_*' in [METADATA] section.")
|
||||
return
|
||||
self.process_monitor = external_process.ProcessMonitor(
|
||||
config=cfg.CONF,
|
||||
resource_type='MetadataPath')
|
||||
self.meta_daemon = host_metadata_proxy.HostMedataHAProxyDaemonMonitor(
|
||||
self.process_monitor,
|
||||
user=str(os.geteuid()),
|
||||
group=str(os.getegid()))
|
||||
|
||||
self.provider_vlan_id = cfg.CONF.METADATA.provider_vlan_id
|
||||
self.provider_cidr = cfg.CONF.METADATA.provider_cidr
|
||||
# TODO(liuyulong): init related flows
|
||||
|
||||
self.provider_gateway_ip = str(netaddr.IPAddress(
|
||||
netaddr.IPNetwork(cfg.CONF.METADATA.provider_cidr).first + 1))
|
||||
|
||||
self._create_internal_port()
|
||||
|
||||
def _set_port_vlan(self):
|
||||
ovsdb = self.meta_br.ovsdb
|
||||
with self.meta_br.ovsdb.transaction() as txn:
|
||||
# When adding the port's tag,
|
||||
# also clear port's vlan_mode and trunks,
|
||||
# which were set to make sure all packets are dropped.
|
||||
txn.add(ovsdb.db_set('Port', self.META_DEV_NAME,
|
||||
('tag', self.provider_vlan_id)))
|
||||
txn.add(ovsdb.db_clear('Port', self.META_DEV_NAME, 'vlan_mode'))
|
||||
txn.add(ovsdb.db_clear('Port', self.META_DEV_NAME, 'trunks'))
|
||||
|
||||
def _create_internal_port(self):
|
||||
attrs = [('type', 'internal'),
|
||||
('external_ids', {'iface-status': 'active',
|
||||
'attached-mac': DEFAULT_META_GATEWAY_MAC})]
|
||||
self.meta_br.replace_port(self.META_DEV_NAME, *attrs)
|
||||
|
||||
ns_dev = ip_lib.IPDevice(self.META_DEV_NAME)
|
||||
|
||||
for _i in range(9):
|
||||
try:
|
||||
ns_dev.link.set_address(DEFAULT_META_GATEWAY_MAC)
|
||||
break
|
||||
except RuntimeError as e:
|
||||
LOG.warning("Got error trying to set mac, retrying: %s", e)
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
ns_dev.link.set_address(DEFAULT_META_GATEWAY_MAC)
|
||||
except RuntimeError as e:
|
||||
msg = _("Failed to set mac address "
|
||||
"for dev %s, error: %s") % (self.META_DEV_NAME, e)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
cidr = "%s/%s" % (
|
||||
self.provider_gateway_ip,
|
||||
netaddr.IPNetwork(self.provider_cidr).prefixlen)
|
||||
ns_dev.addr.add(cidr)
|
||||
ns_dev.link.set_up()
|
||||
|
||||
self.meta_br.set_value_to_other_config(
|
||||
self.META_DEV_NAME,
|
||||
"tag",
|
||||
self.provider_vlan_id)
|
||||
self._set_port_vlan()
|
||||
|
||||
def _reload_host_metadata_proxy(self, force_reload=False):
|
||||
if (not cfg.CONF.METADATA.nova_metadata_host or
|
||||
not cfg.CONF.METADATA.nova_metadata_port):
|
||||
LOG.warning("Nova metadata API related options are not set. "
|
||||
"Host metadata haproxy will not start.")
|
||||
return
|
||||
if not force_reload and not self.instance_infos:
|
||||
return
|
||||
# Haproxy does not suport 'kill -HUP' to reload config file,
|
||||
# so just kill it and then re-spawn.
|
||||
self.meta_daemon.disable()
|
||||
self.meta_daemon.config(list(self.instance_infos.values()))
|
||||
if self.instance_infos:
|
||||
self.meta_daemon.enable()
|
||||
|
||||
def _get_port_info(self, port_detail):
|
||||
device_owner = port_detail['device_owner']
|
||||
if not device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX):
|
||||
return
|
||||
|
||||
port = port_detail['vif_port']
|
||||
provider_ip = self.int_br.get_value_from_other_config(
|
||||
port.port_name, 'provider_ip')
|
||||
provider_mac = self.int_br.get_value_from_other_config(
|
||||
port.port_name, 'provider_mac')
|
||||
|
||||
ins_info = self.ext_api.get_provider_ip_info(port_detail['port_id'],
|
||||
provider_ip,
|
||||
provider_mac)
|
||||
if not ins_info:
|
||||
LOG.info("Failed to get port %s instance provider IP info.",
|
||||
port_detail['port_id'])
|
||||
return
|
||||
self.instance_infos[port_detail['port_id']] = ins_info
|
||||
if not provider_ip or provider_ip != ins_info['provider_ip']:
|
||||
self.int_br.set_value_to_other_config(
|
||||
port.port_name,
|
||||
'provider_ip',
|
||||
ins_info['provider_ip'])
|
||||
if not provider_mac:
|
||||
self.int_br.set_value_to_other_config(
|
||||
port.port_name,
|
||||
'provider_mac',
|
||||
ins_info['provider_port_mac'])
|
||||
|
||||
vlan = self.int_br.get_value_from_other_config(
|
||||
port.port_name, 'tag', int)
|
||||
|
||||
port_info = {"port_id": port_detail['port_id'],
|
||||
"device_owner": device_owner,
|
||||
"port_name": port.port_name,
|
||||
"vlan": vlan,
|
||||
"mac_address": port_detail["mac_address"],
|
||||
"fixed_ips": port_detail["fixed_ips"],
|
||||
"ofport": port.ofport,
|
||||
"network_id": port_detail['network_id']}
|
||||
|
||||
LOG.debug("Metadata path got the port information: %s ",
|
||||
port_info)
|
||||
return port_info
|
||||
|
||||
def handle_port(self, context, port_detail):
|
||||
try:
|
||||
port_info = self._get_port_info(port_detail)
|
||||
if not port_info:
|
||||
return
|
||||
self.set_port_info_cache(port_detail['port_id'], port_info)
|
||||
except Exception as err:
|
||||
LOG.info("Failed to get or set port %s info, error: %s",
|
||||
port_detail['port_id'], err)
|
||||
else:
|
||||
# TODO(liuyulong): Add flows for metadata
|
||||
self._reload_host_metadata_proxy()
|
||||
|
||||
def _get_fixed_ip(self, port_info):
|
||||
for ip in port_info['fixed_ips']:
|
||||
ip_addr = netaddr.IPAddress(ip['ip_address'])
|
||||
if ip_addr.version == constants.IP_VERSION_4:
|
||||
return ip['ip_address']
|
||||
|
||||
def delete_port(self, context, port_detail):
|
||||
ins_info = self.instance_infos.pop(port_detail['port_id'], None)
|
||||
self._reload_host_metadata_proxy(force_reload=True)
|
||||
if not ins_info:
|
||||
return
|
||||
# TODO(liuyulong): Remove flows for metadata
|
||||
self.ext_api.remove_allocated_ip(ins_info['provider_ip'])
|
||||
self.ext_api.remove_allocated_mac(ins_info['provider_port_mac'])
|
@@ -257,6 +257,29 @@ local_ip_opts = [
|
||||
]
|
||||
|
||||
|
||||
metadata_opts = [
|
||||
cfg.StrOpt('provider_cidr', default='240.0.0.0/16',
|
||||
help=_("Local metadata CIDR for VMs metadata traffic, "
|
||||
"will be used as the IP range to generate the "
|
||||
"VM's metadata IP.")),
|
||||
cfg.IntOpt('provider_vlan_id', default=1,
|
||||
help=_("The metadata tap device local vlan ID. This is only "
|
||||
"available on the metadata bridge device.")),
|
||||
cfg.StrOpt('provider_base_mac', default="fa:16:ee:00:00:00",
|
||||
help=_("The base MAC address Neutron Openvswitch agent "
|
||||
"will use for metadata traffic.")),
|
||||
cfg.IntOpt('host_proxy_listen_port', default=80,
|
||||
help=_("Host haproxy listen port for metadata path. This "
|
||||
"is transparent for metadata traffic, VMs still try to "
|
||||
"access 169.254.169.254:80 for metadata. But in "
|
||||
"the metadata datapath flow pipeline, the destination "
|
||||
"TCP port 80 will be changed to the value of "
|
||||
"`host_proxy_listen_port` which the host haproxy "
|
||||
"will listen on. For return traffic, the TCP source "
|
||||
"port will be changed back to 80.")),
|
||||
]
|
||||
|
||||
|
||||
def register_ovs_agent_opts(cfg=cfg.CONF):
|
||||
cfg.register_opts(ovs_opts, "OVS")
|
||||
cfg.register_opts(agent_opts, "AGENT")
|
||||
@@ -264,6 +287,7 @@ def register_ovs_agent_opts(cfg=cfg.CONF):
|
||||
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")
|
||||
cfg.register_opts(metadata_opts, "METADATA")
|
||||
|
||||
|
||||
def register_ovs_opts(cfg=cfg.CONF):
|
||||
|
@@ -342,6 +342,7 @@ def list_ovs_opts():
|
||||
neutron.conf.agent.common.DHCP_PROTOCOL_OPTS)),
|
||||
('metadata',
|
||||
itertools.chain(
|
||||
neutron.conf.plugins.ml2.drivers.ovs_conf.metadata_opts,
|
||||
meta_conf.METADATA_PROXY_HANDLER_OPTS))
|
||||
]
|
||||
|
||||
|
@@ -165,6 +165,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
||||
|
||||
self.enable_openflow_dhcp = 'dhcp' in self.ext_manager.names()
|
||||
self.enable_local_ips = 'local_ip' in self.ext_manager.names()
|
||||
self.enable_openflow_metadata = (
|
||||
'metadata_path' in self.ext_manager.names())
|
||||
|
||||
self.fullsync = False
|
||||
# init bridge classes with configured datapath type.
|
||||
@@ -263,6 +265,11 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
||||
self.phys_brs = {}
|
||||
self.int_ofports = {}
|
||||
self.phys_ofports = {}
|
||||
|
||||
if (self.enable_openflow_metadata and
|
||||
'meta' not in self.bridge_mappings):
|
||||
self.bridge_mappings['meta'] = 'br-meta'
|
||||
|
||||
self.setup_physical_bridges(self.bridge_mappings)
|
||||
self.vlan_manager = vlanmanager.LocalVlanManager()
|
||||
|
||||
|
@@ -0,0 +1,203 @@
|
||||
# Copyright (c) 2023 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 neutron_lib import context
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.agent.common import ovs_lib
|
||||
from neutron.agent.l2.extensions.metadata import metadata_path
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
from neutron.conf.plugins.ml2.drivers import ovs_conf
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent \
|
||||
import ovs_agent_extension_api as ovs_ext_api
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class MetadataPathAgentExtensionTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MetadataPathAgentExtensionTestCase, self).setUp()
|
||||
ovs_conf.register_ovs_agent_opts(cfg=cfg.CONF)
|
||||
cfg.CONF.set_override('provider_cidr', '240.0.0.0/31', 'METADATA')
|
||||
self.context = context.get_admin_context()
|
||||
self.int_br = mock.Mock()
|
||||
self.meta_br = mock.Mock()
|
||||
self.plugin_rpc = mock.Mock()
|
||||
self.remote_resource_cache = mock.Mock()
|
||||
self.plugin_rpc.remote_resource_cache = self.remote_resource_cache
|
||||
self.meta_ext = metadata_path.MetadataPathAgentExtension()
|
||||
self.bridge_mappings = {"meta": "br-meta"}
|
||||
self.int_ofport = 200
|
||||
self.phys_ofport = 100
|
||||
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
|
||||
self.int_br,
|
||||
tun_br=mock.Mock(),
|
||||
phys_brs={"meta": self.meta_br},
|
||||
plugin_rpc=self.plugin_rpc,
|
||||
phys_ofports={"meta": self.phys_ofport},
|
||||
bridge_mappings=self.bridge_mappings)
|
||||
self.meta_ext.consume_api(self.agent_api)
|
||||
mock.patch(
|
||||
"neutron.agent.linux.ip_lib.IpLinkCommand.set_address").start()
|
||||
mock.patch(
|
||||
"neutron.agent.linux.ip_lib.IpAddrCommand.add").start()
|
||||
mock.patch(
|
||||
"neutron.agent.linux.ip_lib.IpLinkCommand.set_up").start()
|
||||
self.meta_ext._set_port_vlan = mock.Mock()
|
||||
self.meta_ext.initialize(None, None)
|
||||
# set int_br back to mock
|
||||
self.meta_ext.int_br = self.int_br
|
||||
# set meta_br back to mock
|
||||
self.meta_ext.meta_br = self.meta_br
|
||||
self.get_port_ofport = mock.patch.object(
|
||||
self.int_br, 'get_port_ofport',
|
||||
return_value=self.int_ofport).start()
|
||||
|
||||
self.meta_daemon = mock.Mock()
|
||||
self.meta_ext.meta_daemon = mock.Mock()
|
||||
|
||||
self.port_provider_ip = "100.100.100.100"
|
||||
self.port_provider_mac = "fa:16:ee:11:22:33"
|
||||
|
||||
def m_get_value_from_ovsdb_other_config(p, key, value_type=None):
|
||||
if key == "provider_ip":
|
||||
return self.port_provider_ip
|
||||
if key == "provider_mac":
|
||||
return self.port_provider_mac
|
||||
|
||||
mock.patch.object(
|
||||
self.int_br, 'get_value_from_other_config',
|
||||
side_effect=m_get_value_from_ovsdb_other_config).start()
|
||||
mock.patch.object(
|
||||
self.int_br, 'set_value_to_other_config').start()
|
||||
|
||||
mock.patch.object(
|
||||
self.meta_br, 'set_value_to_other_config').start()
|
||||
|
||||
def test_handle_port(self):
|
||||
port_mac_address = "aa:aa:aa:aa:aa:aa"
|
||||
port_name = "tap-p1"
|
||||
port_id = "p1"
|
||||
port_ofport = 1
|
||||
port_device_owner = "compute:test"
|
||||
with mock.patch.object(self.meta_ext.meta_daemon,
|
||||
"config") as h_config, mock.patch.object(
|
||||
self.meta_ext.ext_api,
|
||||
"get_provider_ip_info") as get_p_info:
|
||||
get_p_info.return_value = {
|
||||
'instance_id': 'instance_uuid_1',
|
||||
'project_id': 'project_id_1',
|
||||
'provider_ip': self.port_provider_ip,
|
||||
'provider_port_mac': self.port_provider_mac
|
||||
}
|
||||
|
||||
port = {"port_id": port_id,
|
||||
"fixed_ips": [{"ip_address": "1.1.1.1",
|
||||
"subnet_id": "1"}],
|
||||
"vif_port": ovs_lib.VifPort(port_name, port_ofport,
|
||||
port_id,
|
||||
port_mac_address, "br-int"),
|
||||
"device_owner": port_device_owner,
|
||||
"network_id": "net_id_1",
|
||||
"mac_address": port_mac_address}
|
||||
self.meta_ext.handle_port(self.context, port)
|
||||
|
||||
get_p_info.assert_called_once_with(
|
||||
port['port_id'],
|
||||
self.port_provider_ip,
|
||||
self.port_provider_mac)
|
||||
h_config.assert_called_once_with(
|
||||
list(self.meta_ext.instance_infos.values()))
|
||||
|
||||
def test_get_port_no_more_provider_ip(self):
|
||||
|
||||
def m_get_value_from_ovsdb_other_config(p, key, value_type=None):
|
||||
if key == "provider_ip":
|
||||
return
|
||||
if key == "provider_mac":
|
||||
return
|
||||
|
||||
mock.patch.object(
|
||||
self.int_br, 'get_value_from_other_config',
|
||||
side_effect=m_get_value_from_ovsdb_other_config).start()
|
||||
mock.patch.object(
|
||||
self.int_br, 'set_value_to_other_config').start()
|
||||
|
||||
port_device_owner = "compute:test"
|
||||
|
||||
class Port(object):
|
||||
def __init__(self):
|
||||
self.device_id = "d1"
|
||||
self.project_id = "p1"
|
||||
|
||||
with mock.patch.object(self.meta_ext.meta_daemon,
|
||||
"config"), mock.patch.object(
|
||||
self.meta_ext.ext_api.cache_api,
|
||||
"get_resource_by_id",
|
||||
return_value=Port()) as get_res:
|
||||
port1_mac_address = "aa:aa:aa:aa:aa:aa"
|
||||
port1_name = "tap-p1"
|
||||
port1_id = "p1"
|
||||
port1_ofport = 1
|
||||
port1 = {"port_id": port1_id,
|
||||
"fixed_ips": [{"ip_address": "1.1.1.1",
|
||||
"subnet_id": "1"}],
|
||||
"vif_port": ovs_lib.VifPort(port1_name, port1_ofport,
|
||||
port1_id,
|
||||
port1_mac_address, "br-int"),
|
||||
"device_owner": port_device_owner,
|
||||
"network_id": "net_id_1",
|
||||
"mac_address": port1_mac_address}
|
||||
self.meta_ext.handle_port(self.context, port1)
|
||||
|
||||
get_res.assert_called_once_with(
|
||||
resources.PORT,
|
||||
port1['port_id'])
|
||||
|
||||
port2_id = "p2"
|
||||
self.assertRaises(
|
||||
metadata_path.NoMoreProviderRes,
|
||||
self.meta_ext.ext_api.get_provider_ip_info,
|
||||
port2_id, None, None)
|
||||
|
||||
def test_delete_port(self):
|
||||
port_mac_address = "aa:aa:aa:aa:aa:aa"
|
||||
port_name = "tap-p1"
|
||||
port_id = "p1"
|
||||
port_ofport = 1
|
||||
port_device_owner = "compute:test"
|
||||
with mock.patch.object(self.meta_ext.meta_daemon,
|
||||
"config") as h_config:
|
||||
port = {"port_id": port_id,
|
||||
"fixed_ips": [{"ip_address": "1.1.1.1",
|
||||
"subnet_id": "1"}],
|
||||
"vif_port": ovs_lib.VifPort(port_name, port_ofport,
|
||||
port_id,
|
||||
port_mac_address, "br-int"),
|
||||
"device_owner": port_device_owner,
|
||||
"network_id": "net_id_1",
|
||||
"mac_address": port_mac_address}
|
||||
self.meta_ext.handle_port(self.context, port)
|
||||
instance_info_values = list(self.meta_ext.instance_infos.values())
|
||||
|
||||
self.meta_ext.delete_port(self.context, {"port_id": port_id})
|
||||
h_config.assert_has_calls([mock.call(instance_info_values),
|
||||
mock.call([])])
|
||||
self.assertNotIn(self.port_provider_ip,
|
||||
self.meta_ext.ext_api.allocated_ips)
|
||||
self.assertNotIn(self.port_provider_mac,
|
||||
self.meta_ext.ext_api.allocated_macs)
|
@@ -0,0 +1,13 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new openvswitch agent extension ``metadata_path`` was added to implement
|
||||
a distributed approach for virtual machines to retrieve metadata in
|
||||
each running host without a traditional metadata-agent and its dependent
|
||||
router or DHCP namespace.
|
||||
For a new host, users need to create the OVS bridge
|
||||
named ``br-meta``. The OVS-agent will implicitly add an entry
|
||||
``meta:br-meta`` to the list of ``bridge_mappings``.
|
||||
New config options ``provider_cidr``, ``provider_vlan_id``,
|
||||
``provider_base_mac`` and ``host_proxy_listen_port`` are added to the
|
||||
openvswitch agent ``[METADATA]`` section.
|
@@ -136,6 +136,7 @@ neutron.agent.l2.extensions =
|
||||
log = neutron.services.logapi.agent.log_extension:LoggingExtension
|
||||
dhcp = neutron.agent.l2.extensions.dhcp.extension:DHCPAgentExtension
|
||||
local_ip = neutron.agent.l2.extensions.local_ip:LocalIPAgentExtension
|
||||
metadata_path = neutron.agent.l2.extensions.metadata.metadata_path:MetadataPathAgentExtension
|
||||
neutron.agent.l3.extensions =
|
||||
fip_qos = neutron.agent.l3.extensions.qos.fip:FipQosAgentExtension
|
||||
gateway_ip_qos = neutron.agent.l3.extensions.qos.gateway_ip:RouterGatewayIPQosAgentExtension
|
||||
|
Reference in New Issue
Block a user