Fullstack case for metadata path

This case has following workflow:
1. start a neutron environment with 1 neutron-server and 1 ovs-agent
2. boot 2 fake VMs (namespace) to install and verify the installed flows.

Change-Id: Id55deca434f82976a58944146e4d756e52868a33
This commit is contained in:
LIU Yulong
2023-05-12 15:48:00 +08:00
parent 8e2e21d85e
commit 907a2f6317
6 changed files with 445 additions and 6 deletions

View File

@@ -824,6 +824,22 @@ class OVSBridgeFixture(fixtures.Fixture):
self.addCleanup(self.bridge.destroy)
class OVSMetaBridgeFixture(fixtures.Fixture):
"""Create an OVS bridge.
:ivar bridge: created bridge
:type bridge: OVSBridge
"""
def __init__(self, name):
super().__init__()
self.name = name
def _setUp(self):
ovs = ovs_lib.BaseOVS()
self.bridge = ovs.add_bridge(self.name)
self.addCleanup(self.bridge.destroy)
class OVSTrunkBridgeFixture(OVSBridgeFixture):
"""This bridge doesn't generate the name."""
def _setUp(self):

View File

@@ -216,18 +216,21 @@ class OVSConfigFixture(ConfigFixture):
base_filename='openvswitch_agent.ini')
self.tunneling_enabled = self.env_desc.tunneling_enabled
ext_dev = utils.get_rand_device_name(prefix='br-eth')
if host_desc.segmented_physnet:
physnet = PHYSICAL_NETWORK_SEGMENTS_NAME
else:
physnet = PHYSICAL_NETWORK_NAME
self.phy_br_name = utils.get_rand_device_name(prefix='br-eth')
self.meta_br_name = self._generate_meta_bridge()
bridge_mappings = '{}:{}'.format(physnet, self.phy_br_name)
if env_desc.has_metadata:
bridge_mappings += ',{}:{}'.format('meta', self.meta_br_name)
self.config.update({
'ovs': {
'local_ip': local_ip,
'integration_bridge': self._generate_integration_bridge(),
'bridge_mappings': '{}:{}'.format(physnet, ext_dev),
'bridge_mappings': bridge_mappings,
'of_inactivity_probe': '0',
'ovsdb_debug': 'True',
'qos_meter_bandwidth': str(
@@ -254,7 +257,8 @@ class OVSConfigFixture(ConfigFixture):
else:
if env_desc.report_bandwidths:
self.config['ovs'][constants.RP_BANDWIDTHS] = \
'{}:{}:{}'.format(ext_dev, MINIMUM_BANDWIDTH_EGRESS_KBPS,
'{}:{}:{}'.format(self.phy_br_name,
MINIMUM_BANDWIDTH_EGRESS_KBPS,
MINIMUM_BANDWIDTH_INGRESS_KBPS)
if env_desc.qos:
@@ -281,6 +285,17 @@ class OVSConfigFixture(ConfigFixture):
if host_desc.firewall_driver == 'openvswitch':
self.config['local_ip'] = {'static_nat': 'True'}
if env_desc.has_metadata:
self.config['agent']['extensions'] = 'metadata_path'
self.config.update({
'METADATA': {
'metadata_proxy_shared_secret': 'secret',
'nova_metadata_host': env_desc.metadata_host,
'nova_metadata_port': str(env_desc.metadata_port),
'host_proxy_listen_port': str(env_desc.hp_listen_port),
}
})
def _setUp(self):
self.config['ovs'].update({
'of_listen_port': self.useFixture(
@@ -296,6 +311,9 @@ class OVSConfigFixture(ConfigFixture):
def _generate_tunnel_bridge(self):
return utils.get_rand_device_name(prefix='br-tun')
def _generate_meta_bridge(self):
return utils.get_rand_device_name(prefix='br-meta')
def _generate_int_peer(self):
return utils.get_rand_device_name(prefix='patch-tun')
@@ -313,7 +331,10 @@ class OVSConfigFixture(ConfigFixture):
return self.config.ovs.integration_bridge
def get_br_phys_name(self):
return self.config.ovs.bridge_mappings.split(':')[1]
return self.phy_br_name
def get_br_meta_name(self):
return self.meta_br_name
def get_br_tun_name(self):
return self.config.ovs.tunnel_bridge
@@ -355,6 +376,20 @@ class PlacementConfigFixture(ConfigFixture):
})
class MetadataConfigFixture(ConfigFixture):
def __init__(self, env_desc, host_desc, temp_dir):
super().__init__(
env_desc, host_desc, temp_dir, base_filename='metadata.ini')
self.config.update({
'DEFAULT': {
'debug': 'True',
'metadata_host': self.env_desc.metadata_host,
'metadata_port': str(self.env_desc.metadata_port)
}
})
class L3ConfigFixture(ConfigFixture):
def __init__(self, env_desc, host_desc, temp_dir, integration_bridge=None):

View File

@@ -46,7 +46,9 @@ class EnvironmentDescription:
api_workers=1,
enable_traditional_dhcp=True, local_ip_ext=False,
quota_driver=quota_conf.QUOTA_DB_DRIVER,
use_meter_bandwidth_limit=False):
use_meter_bandwidth_limit=False,
has_metadata=False, metadata_host=None, metadata_port=None,
host_proxy_listen_port=None):
self.network_type = network_type
self.l2_pop = l2_pop
self.qos = qos
@@ -75,6 +77,10 @@ class EnvironmentDescription:
self.service_plugins += ',local_ip'
self.quota_driver = quota_driver
self.use_meter_bandwidth_limit = use_meter_bandwidth_limit
self.has_metadata = has_metadata
self.metadata_host = metadata_host
self.metadata_port = metadata_port
self.hp_listen_port = host_proxy_listen_port
@property
def tunneling_enabled(self):
@@ -183,6 +189,12 @@ class Host(fixtures.Fixture):
self.env_desc, self.host_desc,
self.test_name, self.neutron_config, agent_cfg_fixture))
if self.env_desc.has_metadata:
self.br_meta = self.useFixture(
net_helpers.OVSMetaBridgeFixture(
self.ovs_agent.agent_cfg_fixture.get_br_meta_name())
).bridge
if self.host_desc.l3_agent:
self.l3_agent_cfg_fixture = self.useFixture(
config.L3ConfigFixture(
@@ -415,6 +427,17 @@ class Environment(fixtures.Fixture):
placement_cfg_fixture)
)
if self.env_desc.has_metadata:
metadata_cfg_fixture = self.useFixture(
config.MetadataConfigFixture(self.env_desc, self.hosts_desc,
self.temp_dir)
)
self.metadata = self.useFixture(
process.MetadataFixture(
self.env_desc, self.hosts_desc, self.test_name,
metadata_cfg_fixture)
)
self.hosts = [self._create_host(desc) for desc in self.hosts_desc]
self.wait_until_env_is_up()

View File

@@ -314,6 +314,27 @@ class PlacementFixture(ServiceFixture):
config_filenames=[self.placement_cfg_fixture.filename]))
class MetadataFixture(ServiceFixture):
def __init__(self, env_desc, host_desc, test_name, metadata_cfg_fixture):
super().__init__()
self.env_desc = env_desc
self.host_desc = host_desc
self.test_name = test_name
self.metadata_cfg_fixture = metadata_cfg_fixture
self.metadata_config = self.metadata_cfg_fixture.config
def _setUp(self):
self.process_fixture = self.useFixture(ProcessFixture(
test_name=self.test_name,
process_name='metadata',
exec_name=shutil.which(
'metadata.py', path=os.path.join(fullstack_base.ROOTDIR,
'servers')
),
config_filenames=[self.metadata_cfg_fixture.filename]))
class SRIOVAgentFixture(ServiceFixture):
def __init__(self, env_desc, host_desc,

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python
#
# 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 sys
from wsgiref import simple_server as wsgi_simple_server
from oslo_config import cfg
from oslo_log import log as logging
from neutron.common import config as common_config
LOG = logging.getLogger(__name__)
metadata_opts = [
cfg.StrOpt('metadata_host', default='127.0.0.1'),
cfg.IntOpt('metadata_port', default=8775)
]
cfg.CONF.register_opts(metadata_opts)
class FakeMetadata():
def wsgi_app(self, env, start_response):
response_headers = [('Content-Type', 'application/json')]
http_status = '200 OK'
LOG.info("HTTP_X_INSTANCE_ID: %s", env.get('HTTP_X_INSTANCE_ID'))
LOG.info("HTTP_X_TENANT_ID: %s", env.get('HTTP_X_TENANT_ID'))
LOG.info("HTTP_X_INSTANCE_ID_SIGNATURE: %s",
env.get('HTTP_X_INSTANCE_ID_SIGNATURE'))
# Send the headers back for verify the path
response_headers += [
('RESP_INSTANCE_ID', env.get('HTTP_X_INSTANCE_ID')),
('RESP_TENANT_ID', env.get('HTTP_X_TENANT_ID')),
('RESP_INSTANCE_ID_SIGNATURE',
env.get('HTTP_X_INSTANCE_ID_SIGNATURE'))]
start_response(http_status, response_headers)
return [b"Metadata OK"]
if __name__ == "__main__":
common_config.register_common_config_options()
common_config.init(sys.argv[1:])
common_config.setup_logging()
metadata_host = cfg.CONF.metadata_host
metadata_port = cfg.CONF.metadata_port
LOG.info("Metadata fixture started on port: %s", metadata_port)
mock_metadata = FakeMetadata()
wsgi_simple_server.make_server(
metadata_host, metadata_port, mock_metadata.wsgi_app).serve_forever()

View File

@@ -0,0 +1,279 @@
# 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 re
import netaddr
from neutron_lib import constants
from neutron_lib.plugins.ml2 import ovs_constants as p_const
from neutron_lib.plugins import utils as p_utils
from oslo_log import log as logging
from oslo_utils import uuidutils
from neutron.common import utils as common_utils
from neutron.tests.common.exclusive_resources import ip_network
from neutron.tests.fullstack import base
from neutron.tests.fullstack.resources import environment
from neutron.tests.fullstack.resources import machine
LOG = logging.getLogger(__name__)
METADATA_REQUEST_TIMEOUT = 60
METADATA_REQUEST_SLEEP = 5
TOO_MANY_REQUESTS_CODE = '429'
class OvsMetadataExtensionTestCase(base.BaseFullStackTestCase):
number_of_hosts = 1
def setUp(self):
host_desc = [
environment.HostDescription(
l2_agent_type=constants.AGENT_TYPE_OVS,
firewall_driver='openvswitch',
l3_agent=True,
dhcp_agent=False) for _ in range(self.number_of_hosts)]
env_desc = environment.EnvironmentDescription(
mech_drivers='openvswitch',
has_metadata=True, metadata_host='127.0.0.1',
metadata_port=58775,
host_proxy_listen_port=55555,
enable_traditional_dhcp=False)
env = environment.Environment(env_desc, host_desc)
super().setUp(env)
self.tenant_id = uuidutils.generate_uuid()
self.vm_id_1 = uuidutils.generate_uuid()
self.vm_id_2 = uuidutils.generate_uuid()
network = self.safe_client.create_network(
self.tenant_id, name='public', external=True)
cidr = self.useFixture(
ip_network.ExclusiveIPNetwork(
"240.0.0.0", "240.255.255.255", "24")).network
self.safe_client.create_subnet(
self.tenant_id, network['id'], cidr)
router = self.safe_client.create_router(
self.tenant_id, external_network=network['id'])
self.network = self.safe_client.create_network(
self.tenant_id, 'network-test')
subnet_routes_v4 = [
{"destination": "1.1.1.0/24", "nexthop": "10.0.0.100"},
{"destination": "2.2.2.2/32", "nexthop": "10.0.0.101"}]
self.subnet_v4 = self.safe_client.create_subnet(
self.tenant_id, self.network['id'],
cidr='10.0.0.0/24',
gateway_ip='10.0.0.1',
enable_dhcp=False,
name='subnet-v4-test',
host_routes=subnet_routes_v4)
router_interface_info = self.safe_client.add_router_interface(
router['id'], self.subnet_v4['id'])
self.block_until_port_status_active(
router_interface_info['port_id'])
subnet_routes_v6 = [
{"destination": "2001:4860:4860::8888/128",
"nexthop": "fda7:a5cc:3460:1::1"},
{"destination": "1234:5678:abcd::/64",
"nexthop": "fda7:a5cc:3460:1::fff"}]
self.subnet_v6 = self.safe_client.create_subnet(
self.tenant_id, self.network['id'],
cidr='fda7:a5cc:3460:1::/64',
gateway_ip='fda7:a5cc:3460:1::1',
enable_dhcp=True,
ipv6_address_mode="dhcpv6-stateful",
ipv6_ra_mode="dhcpv6-stateful",
ip_version=6,
name='subnet-v6-test',
host_routes=subnet_routes_v6)
# Need router radvd to send IPv6 address prefix to make the default
# route work.
router_interface_info = self.safe_client.add_router_interface(
router['id'], self.subnet_v6['id'])
self.block_until_port_status_active(
router_interface_info['port_id'])
def block_until_port_status_active(self, port_id):
def is_port_status_active():
port = self.client.show_port(port_id)
return port['port']['status'] == 'ACTIVE'
common_utils.wait_until_true(lambda: is_port_status_active(), sleep=1)
def _prepare_vms(self):
sgs = [self.safe_client.create_security_group(self.tenant_id)
for _ in range(2)]
port1 = self.safe_client.create_port(
self.tenant_id, self.network['id'],
self.environment.hosts[0].hostname,
device_owner="compute:test_ovs_meta_1",
device_id=self.vm_id_1,
security_groups=[sgs[0]['id']])
port2 = self.safe_client.create_port(
self.tenant_id, self.network['id'],
self.environment.hosts[0].hostname,
device_owner="compute:test_ovs_meta_2",
device_id=self.vm_id_2,
security_groups=[sgs[1]['id']])
# insert security-group-rules allow icmp
self.safe_client.create_security_group_rule(
self.tenant_id, sgs[0]['id'],
direction=constants.INGRESS_DIRECTION,
ethertype=constants.IPv4,
protocol=constants.PROTO_NAME_ICMP)
self.safe_client.create_security_group_rule(
self.tenant_id, sgs[0]['id'],
direction=constants.INGRESS_DIRECTION,
ethertype=constants.IPv6,
protocol=constants.PROTO_NAME_ICMP)
# insert security-group-rules allow icmp
self.safe_client.create_security_group_rule(
self.tenant_id, sgs[1]['id'],
direction=constants.INGRESS_DIRECTION,
ethertype=constants.IPv4,
protocol=constants.PROTO_NAME_ICMP)
self.safe_client.create_security_group_rule(
self.tenant_id, sgs[1]['id'],
direction=constants.INGRESS_DIRECTION,
ethertype=constants.IPv6,
protocol=constants.PROTO_NAME_ICMP)
vm1 = self.useFixture(
machine.FakeFullstackMachine(
self.environment.hosts[0],
self.network['id'],
self.tenant_id,
self.safe_client,
neutron_port=port1,
use_dhcp=False,
use_dhcp6=False))
vm2 = self.useFixture(
machine.FakeFullstackMachine(
self.environment.hosts[0],
self.network['id'],
self.tenant_id,
self.safe_client,
neutron_port=port2,
use_dhcp=False,
use_dhcp6=False))
return machine.FakeFullstackMachinesList([vm1, vm2])
def _wait_for_metadata_flows_applied(self, vm, table, actions):
def _is_metadata_flow_set(vm, table, actions):
LOG.info("Metadata bridge verify actions: %s", actions)
flows = vm.host.br_meta.dump_flows_for_table(table)
flows_list = flows.splitlines()
LOG.info("Metadata bridge flows_list: %s", flows_list)
pattern = re.compile(
r"^.* table=%s,.* actions=%s" % (table, re.escape(actions)))
for flow in flows_list:
if pattern.match(flow.strip()):
return True
return False
common_utils.wait_until_true(lambda: _is_metadata_flow_set(
vm, table, actions))
def test_ovs_meta_agent_extension_verify_ovs_flows(self):
vms = self._prepare_vms()
vms.block_until_all_boot()
# Check ovs flows
vm_0_provider_ip = vms[0].bridge.get_value_from_other_config(
vms[0].port.name, "provider_ip", value_type=str)
vm_0_provider_mac = vms[0].bridge.get_value_from_other_config(
vms[0].port.name, "provider_mac", value_type=str)
actions_0 = ("strip_vlan,mod_dl_src:%s,"
"mod_nw_src:%s,resubmit(,87)") % (vm_0_provider_mac,
vm_0_provider_ip)
self._wait_for_metadata_flows_applied(vms[0], 80, actions_0)
vm_1_provider_ip = vms[1].bridge.get_value_from_other_config(
vms[1].port.name, "provider_ip", value_type=str)
vm_1_provider_mac = vms[1].bridge.get_value_from_other_config(
vms[1].port.name, "provider_mac", value_type=str)
actions_1 = ("strip_vlan,mod_dl_src:%s,"
"mod_nw_src:%s,resubmit(,87)") % (vm_1_provider_mac,
vm_1_provider_ip)
self._wait_for_metadata_flows_applied(vms[1], 80, actions_1)
tap_meta_ofport = vms[0].host.br_meta.get_port_ofport("tap-meta")
self._wait_for_metadata_flows_applied(
vms[0], 87,
("mod_dl_dst:fa:16:ee:00:00:01,mod_nw_dst:240.0.0.1,"
"mod_tp_dst:55555,output:%s" % tap_meta_ofport))
vm_0_provider_mac_hex = "0x%s" % (
vm_0_provider_mac.replace(":", ""))
vm_0_provider_ip_hex = "%x" % netaddr.IPAddress(vm_0_provider_ip)
vm_0_arp_res_actions = (
"load:0x2->NXM_OF_ARP_OP[],"
"move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],"
"move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],"
"load:%s->NXM_NX_ARP_SHA[],"
"load:0x%s->NXM_OF_ARP_SPA[],"
"move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],"
"mod_dl_src:%s,IN_PORT") % (vm_0_provider_mac_hex,
vm_0_provider_ip_hex,
vm_0_provider_mac)
self._wait_for_metadata_flows_applied(
vms[0], 90, vm_0_arp_res_actions)
vm_1_provider_mac_hex = "0x%s" % (vm_1_provider_mac.replace(":", ""))
vm_1_provider_ip_hex = "%x" % netaddr.IPAddress(vm_1_provider_ip)
vm_1_arp_res_actions = (
"load:0x2->NXM_OF_ARP_OP[],"
"move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],"
"move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],"
"load:%s->NXM_NX_ARP_SHA[],"
"load:0x%s->NXM_OF_ARP_SPA[],"
"move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],"
"mod_dl_src:%s,IN_PORT") % (vm_1_provider_mac_hex,
vm_1_provider_ip_hex,
vm_1_provider_mac)
self._wait_for_metadata_flows_applied(
vms[1], 90, vm_1_arp_res_actions)
local_vlan = vms[0].bridge.get_port_tag_by_name(vms[0].port.name)
patch_name = p_utils.get_interface_name(
vms[0].host.br_meta.br_name,
prefix=p_const.PEER_PHYSICAL_PREFIX)
patch_ofport = vms[0].host.br_meta.get_port_ofport(patch_name)
self._wait_for_metadata_flows_applied(
vms[0], 91,
("mod_vlan_vid:%s,mod_dl_dst:%s,"
"mod_nw_src:169.254.169.254,mod_nw_dst:%s,mod_tp_src:80,"
"output:%s" % (local_vlan, vms[0].neutron_port['mac_address'],
vms[0].ip, patch_ofport)))
local_vlan = vms[1].bridge.get_port_tag_by_name(vms[1].port.name)
patch_name = p_utils.get_interface_name(
vms[1].host.br_meta.br_name,
prefix=p_const.PEER_PHYSICAL_PREFIX)
patch_ofport = vms[1].host.br_meta.get_port_ofport(patch_name)
self._wait_for_metadata_flows_applied(
vms[1], 91,
("mod_vlan_vid:%s,mod_dl_dst:%s,"
"mod_nw_src:169.254.169.254,mod_nw_dst:%s,mod_tp_src:80,"
"output:%s" % (local_vlan, vms[1].neutron_port['mac_address'],
vms[1].ip, patch_ofport)))