Add fullstack test case for OVS DHCP extension
This patch will create two fake VMs to verify the DHCP R/R for its NIC. Ping 4/6 will be run after the interface request and config the IP address via DHCPv4/6 by using dhclient. For fullstack test fake environment, setting enable_traditional_dhcp to False means enable DHCP extension for OVS agent automatically. Partially-Implements: bp/distributed-dhcp-for-ml2-ovs Closes-Bug: #1900934 Change-Id: I40d6464953fbc4d4ca2c05a6051eba43cd05fedd
This commit is contained in:
parent
3127bd1d57
commit
4b22eea4be
|
@ -120,7 +120,8 @@ class ClientFixture(fixtures.Fixture):
|
|||
def create_subnet(self, tenant_id, network_id,
|
||||
cidr=None, gateway_ip=None, name=None, enable_dhcp=True,
|
||||
ipv6_address_mode='slaac', ipv6_ra_mode='slaac',
|
||||
subnetpool_id=None, ip_version=None):
|
||||
subnetpool_id=None, ip_version=None,
|
||||
host_routes=None):
|
||||
resource_type = 'subnet'
|
||||
|
||||
name = name or utils.get_rand_name(prefix=resource_type)
|
||||
|
@ -138,6 +139,8 @@ class ClientFixture(fixtures.Fixture):
|
|||
spec['subnetpool_id'] = subnetpool_id
|
||||
if cidr:
|
||||
spec['cidr'] = cidr
|
||||
if host_routes:
|
||||
spec['host_routes'] = host_routes
|
||||
|
||||
return self._create_resource(resource_type, spec)
|
||||
|
||||
|
|
|
@ -131,6 +131,9 @@ class NeutronConfigFixture(ConfigFixture):
|
|||
self.config['DEFAULT']['network_scheduler_driver'] = (
|
||||
env_desc.dhcp_scheduler_class)
|
||||
|
||||
self.config['DEFAULT']['enable_traditional_dhcp'] = str(
|
||||
env_desc.enable_traditional_dhcp)
|
||||
|
||||
net_helpers.set_local_port_range(CLIENT_CONN_PORT_START,
|
||||
CLIENT_CONN_PORT_END)
|
||||
|
||||
|
@ -253,6 +256,14 @@ class OVSConfigFixture(ConfigFixture):
|
|||
'local_output_log_base':
|
||||
self._generate_temp_log_file(test_name)}
|
||||
})
|
||||
if not env_desc.enable_traditional_dhcp:
|
||||
self.config['agent']['extensions'] = 'dhcp'
|
||||
self.config.update({
|
||||
'dhcp': {
|
||||
'enable_ipv6': 'True',
|
||||
'renewal_time': '0',
|
||||
'rebinding_time': '0'}
|
||||
})
|
||||
|
||||
def _setUp(self):
|
||||
self.config['ovs'].update({
|
||||
|
|
|
@ -41,7 +41,8 @@ class EnvironmentDescription(object):
|
|||
debug_iptables=False, log=False, report_bandwidths=False,
|
||||
has_placement=False, placement_port=None,
|
||||
dhcp_scheduler_class=None, ml2_extension_drivers=None,
|
||||
api_workers=1):
|
||||
api_workers=1,
|
||||
enable_traditional_dhcp=True):
|
||||
self.network_type = network_type
|
||||
self.l2_pop = l2_pop
|
||||
self.qos = qos
|
||||
|
@ -64,6 +65,7 @@ class EnvironmentDescription(object):
|
|||
self.service_plugins += ',log'
|
||||
self.ml2_extension_drivers = ml2_extension_drivers
|
||||
self.api_workers = api_workers
|
||||
self.enable_traditional_dhcp = enable_traditional_dhcp
|
||||
|
||||
@property
|
||||
def tunneling_enabled(self):
|
||||
|
|
|
@ -45,13 +45,21 @@ class FakeFullstackMachinesList(list):
|
|||
for vm_1, vm_2 in itertools.permutations(self, 2):
|
||||
vm_1.block_until_ping(vm_2.ip)
|
||||
|
||||
def ping6_all(self):
|
||||
# Generate an iterable of all unique pairs. For example:
|
||||
# itertools.permutations(range(3), 2) results in:
|
||||
# ((0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1))
|
||||
for vm_1, vm_2 in itertools.permutations(self, 2):
|
||||
vm_1.block_until_ping(vm_2.ipv6)
|
||||
|
||||
|
||||
class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
||||
NO_RESOLV_CONF_DHCLIENT_SCRIPT_PATH = (
|
||||
spawn.find_executable(FULLSTACK_DHCLIENT_SCRIPT))
|
||||
|
||||
def __init__(self, host, network_id, tenant_id, safe_client,
|
||||
neutron_port=None, bridge_name=None, use_dhcp=False):
|
||||
neutron_port=None, bridge_name=None, use_dhcp=False,
|
||||
use_dhcp6=False):
|
||||
super(FakeFullstackMachine, self).__init__()
|
||||
self.host = host
|
||||
self.tenant_id = tenant_id
|
||||
|
@ -61,6 +69,7 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
|||
self.bridge_name = bridge_name
|
||||
self.use_dhcp = use_dhcp
|
||||
self.dhclient_async = None
|
||||
self.use_dhcp6 = use_dhcp6
|
||||
|
||||
def _setUp(self):
|
||||
super(FakeFullstackMachine, self)._setUp()
|
||||
|
@ -115,6 +124,9 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
|||
# v6Address/default_route is auto-configured.
|
||||
self._ipv6 = fixed_ip['ip_address']
|
||||
self.gateway_ipv6 = subnet['subnet']['gateway_ip']
|
||||
if self.use_dhcp6:
|
||||
self._configure_ipaddress_via_dhcp(
|
||||
version=constants.IP_VERSION_6)
|
||||
else:
|
||||
self._ip = fixed_ip['ip_address']
|
||||
prefixlen = netaddr.IPNetwork(subnet['subnet']['cidr']).prefixlen
|
||||
|
@ -134,12 +146,13 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
|||
if gateway_ip in net:
|
||||
net_helpers.set_namespace_gateway(self.port, self.gateway_ip)
|
||||
|
||||
def _configure_ipaddress_via_dhcp(self):
|
||||
self._start_async_dhclient()
|
||||
def _configure_ipaddress_via_dhcp(self, version=constants.IP_VERSION_4):
|
||||
self._start_async_dhclient(version)
|
||||
self.addCleanup(self._stop_async_dhclient)
|
||||
|
||||
def _start_async_dhclient(self):
|
||||
cmd = ["dhclient", '-sf', self.NO_RESOLV_CONF_DHCLIENT_SCRIPT_PATH,
|
||||
def _start_async_dhclient(self, version=constants.IP_VERSION_4):
|
||||
cmd = ["dhclient", '-%s' % version,
|
||||
'-sf', self.NO_RESOLV_CONF_DHCLIENT_SCRIPT_PATH,
|
||||
'--no-pid', '-d', self.port.name]
|
||||
self.dhclient_async = async_process.AsyncProcess(
|
||||
cmd, run_as_root=True, respawn_interval=5,
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
# Copyright (c) 2021 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 neutron_lib import constants
|
||||
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
|
||||
|
||||
|
||||
class OvsDHCPExtensionTestCase(base.BaseFullStackTestCase):
|
||||
number_of_hosts = 1
|
||||
|
||||
def setUp(self):
|
||||
host_desc = [
|
||||
environment.HostDescription(
|
||||
l2_agent_type=constants.AGENT_TYPE_OVS,
|
||||
firewall_driver='openvswitch',
|
||||
# VM needs to receive the RA notification
|
||||
# from radvd which is handled by router and L3 agent.
|
||||
l3_agent=True,
|
||||
dhcp_agent=False) for _ in range(self.number_of_hosts)]
|
||||
env_desc = environment.EnvironmentDescription(
|
||||
mech_drivers='openvswitch',
|
||||
enable_traditional_dhcp=False)
|
||||
env = environment.Environment(env_desc, host_desc)
|
||||
super(OvsDHCPExtensionTestCase, self).setUp(env)
|
||||
self.tenant_id = 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',
|
||||
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_dhcp",
|
||||
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_dhcp",
|
||||
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=True,
|
||||
use_dhcp6=True))
|
||||
|
||||
vm2 = self.useFixture(
|
||||
machine.FakeFullstackMachine(
|
||||
self.environment.hosts[0],
|
||||
self.network['id'],
|
||||
self.tenant_id,
|
||||
self.safe_client,
|
||||
neutron_port=port2,
|
||||
use_dhcp=True,
|
||||
use_dhcp6=True))
|
||||
return machine.FakeFullstackMachinesList([vm1, vm2])
|
||||
|
||||
def test_ovs_dhcp_agent_extension_ping_vms(self):
|
||||
vms = self._prepare_vms()
|
||||
vms.block_until_all_boot()
|
||||
# ping -4 from vm_1 to vm_2
|
||||
vms.ping_all()
|
||||
# ping -6 from vm_1 to vm_2
|
||||
vms.ping6_all()
|
Loading…
Reference in New Issue