Fullstack test for DHCP agent
This patch adds basic support for DHCP agent resource in fullstack tests suite. FakeFullstackMachine can now be set to use DHCP and IP address is then configured with DHCP using dhclient tool. It also adds base tests for basic DHCP agent functionallities: * schedule network to DHCP agent * provide IP address for port (FakeMachine) by DHCP agent Tests are done with Linuxbridge and Openvswitch L2 agents with basic configuration in both cases. Change-Id: I2ecf15c12d5d5e957fe1b08113b5cc13e746ec2d
This commit is contained in:
parent
920ddeaf58
commit
f38688cfec
@ -12,6 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import fixtures
|
||||
@ -292,3 +294,49 @@ class L3ConfigFixture(ConfigFixture):
|
||||
|
||||
def _generate_namespace_suffix(self):
|
||||
return utils.get_rand_name(prefix='test')
|
||||
|
||||
|
||||
class DhcpConfigFixture(ConfigFixture):
|
||||
|
||||
def __init__(self, env_desc, host_desc, temp_dir, integration_bridge=None):
|
||||
super(DhcpConfigFixture, self).__init__(
|
||||
env_desc, host_desc, temp_dir, base_filename='dhcp_agent.ini')
|
||||
|
||||
if host_desc.l2_agent_type == constants.AGENT_TYPE_OVS:
|
||||
self._prepare_config_with_ovs_agent(integration_bridge)
|
||||
elif host_desc.l2_agent_type == constants.AGENT_TYPE_LINUXBRIDGE:
|
||||
self._prepare_config_with_linuxbridge_agent()
|
||||
self.config['DEFAULT'].update({
|
||||
'debug': 'True',
|
||||
'dhcp_confs': self._generate_dhcp_path(),
|
||||
})
|
||||
|
||||
def _setUp(self):
|
||||
super(DhcpConfigFixture, self)._setUp()
|
||||
self.addCleanup(self._clean_dhcp_path)
|
||||
|
||||
def _prepare_config_with_ovs_agent(self, integration_bridge):
|
||||
self.config.update({
|
||||
'DEFAULT': {
|
||||
'interface_driver': 'openvswitch',
|
||||
'ovs_integration_bridge': integration_bridge,
|
||||
}
|
||||
})
|
||||
|
||||
def _prepare_config_with_linuxbridge_agent(self):
|
||||
self.config.update({
|
||||
'DEFAULT': {
|
||||
'interface_driver': 'linuxbridge',
|
||||
}
|
||||
})
|
||||
|
||||
def _generate_dhcp_path(self):
|
||||
# NOTE(slaweq): dhcp_conf path needs to be directory with read
|
||||
# permission for everyone, otherwise dnsmasq process will not be able
|
||||
# to read his configs
|
||||
self.dhcp_path = tempfile.mkdtemp(prefix="dhcp_configs_", dir="/tmp/")
|
||||
os.chmod(self.dhcp_path, 0o755)
|
||||
return self.dhcp_path
|
||||
|
||||
def _clean_dhcp_path(self):
|
||||
shutil.rmtree(self.dhcp_path, ignore_errors=True)
|
||||
|
@ -53,12 +53,13 @@ class HostDescription(object):
|
||||
What agents should the host spawn? What mode should each agent operate
|
||||
under?
|
||||
"""
|
||||
def __init__(self, l3_agent=False, of_interface='ovs-ofctl',
|
||||
ovsdb_interface='vsctl',
|
||||
def __init__(self, l3_agent=False, dhcp_agent=False,
|
||||
of_interface='ovs-ofctl', ovsdb_interface='vsctl',
|
||||
l2_agent_type=constants.AGENT_TYPE_OVS,
|
||||
firewall_driver='noop'):
|
||||
self.l2_agent_type = l2_agent_type
|
||||
self.l3_agent = l3_agent
|
||||
self.dhcp_agent = dhcp_agent
|
||||
self.of_interface = of_interface
|
||||
self.ovsdb_interface = ovsdb_interface
|
||||
self.firewall_driver = firewall_driver
|
||||
@ -109,6 +110,15 @@ class Host(fixtures.Fixture):
|
||||
self.neutron_config,
|
||||
self.l3_agent_cfg_fixture))
|
||||
|
||||
if self.host_desc.dhcp_agent:
|
||||
self.dhcp_agent = self.useFixture(
|
||||
process.DhcpAgentFixture(
|
||||
self.env_desc, self.host_desc,
|
||||
self.test_name,
|
||||
self.neutron_config,
|
||||
self.dhcp_agent_cfg_fixture,
|
||||
namespace=self.host_namespace))
|
||||
|
||||
def setup_host_with_ovs_agent(self):
|
||||
agent_cfg_fixture = config.OVSConfigFixture(
|
||||
self.env_desc, self.host_desc, self.neutron_config.temp_dir,
|
||||
@ -142,6 +152,13 @@ class Host(fixtures.Fixture):
|
||||
self.l3_agent_cfg_fixture.get_external_bridge())).bridge
|
||||
self.connect_to_external_network(br_ex)
|
||||
|
||||
if self.host_desc.dhcp_agent:
|
||||
self.dhcp_agent_cfg_fixture = self.useFixture(
|
||||
config.DhcpConfigFixture(
|
||||
self.env_desc, self.host_desc,
|
||||
self.neutron_config.temp_dir,
|
||||
self.ovs_agent.agent_cfg_fixture.get_br_int_name()))
|
||||
|
||||
def setup_host_with_linuxbridge_agent(self):
|
||||
#First we need to provide connectivity for agent to prepare proper
|
||||
#bridge mappings in agent's config:
|
||||
@ -173,6 +190,12 @@ class Host(fixtures.Fixture):
|
||||
self.env_desc, self.host_desc,
|
||||
self.neutron_config.temp_dir))
|
||||
|
||||
if self.host_desc.dhcp_agent:
|
||||
self.dhcp_agent_cfg_fixture = self.useFixture(
|
||||
config.DhcpConfigFixture(
|
||||
self.env_desc, self.host_desc,
|
||||
self.neutron_config.temp_dir))
|
||||
|
||||
def _connect_ovs_port(self, cidr_address):
|
||||
ovs_device = self.useFixture(
|
||||
net_helpers.OVSPortFixture(
|
||||
@ -248,6 +271,14 @@ class Host(fixtures.Fixture):
|
||||
def l3_agent(self, agent):
|
||||
self.agents['l3'] = agent
|
||||
|
||||
@property
|
||||
def dhcp_agent(self):
|
||||
return self.agents['dhcp']
|
||||
|
||||
@dhcp_agent.setter
|
||||
def dhcp_agent(self, agent):
|
||||
self.agents['dhcp'] = agent
|
||||
|
||||
@property
|
||||
def ovs_agent(self):
|
||||
return self.agents['ovs']
|
||||
|
@ -18,6 +18,7 @@ import netaddr
|
||||
|
||||
from neutron_lib import constants
|
||||
|
||||
from neutron.agent.linux import async_process
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.common import utils
|
||||
from neutron.extensions import portbindings as pbs
|
||||
@ -43,7 +44,7 @@ class FakeFullstackMachinesList(list):
|
||||
class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
||||
|
||||
def __init__(self, host, network_id, tenant_id, safe_client,
|
||||
neutron_port=None, bridge_name=None):
|
||||
neutron_port=None, bridge_name=None, use_dhcp=False):
|
||||
super(FakeFullstackMachine, self).__init__()
|
||||
self.host = host
|
||||
self.tenant_id = tenant_id
|
||||
@ -51,6 +52,8 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
||||
self.safe_client = safe_client
|
||||
self.neutron_port = neutron_port
|
||||
self.bridge_name = bridge_name
|
||||
self.use_dhcp = use_dhcp
|
||||
self.dhclient_async = None
|
||||
|
||||
def _setUp(self):
|
||||
super(FakeFullstackMachine, self)._setUp()
|
||||
@ -109,13 +112,36 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
||||
self._ip = fixed_ip['ip_address']
|
||||
prefixlen = netaddr.IPNetwork(subnet['subnet']['cidr']).prefixlen
|
||||
self._ip_cidr = '%s/%s' % (self._ip, prefixlen)
|
||||
|
||||
# TODO(amuller): Support DHCP
|
||||
self.port.addr.add(self.ip_cidr)
|
||||
|
||||
self.gateway_ip = subnet['subnet']['gateway_ip']
|
||||
if self.gateway_ip:
|
||||
net_helpers.set_namespace_gateway(self.port, self.gateway_ip)
|
||||
|
||||
if self.use_dhcp:
|
||||
self._configure_ipaddress_via_dhcp()
|
||||
else:
|
||||
self._configure_static_ipaddress()
|
||||
|
||||
def _configure_static_ipaddress(self):
|
||||
self.port.addr.add(self.ip_cidr)
|
||||
if self.gateway_ip:
|
||||
net_helpers.set_namespace_gateway(self.port, self.gateway_ip)
|
||||
|
||||
def _configure_ipaddress_via_dhcp(self):
|
||||
self._start_async_dhclient()
|
||||
self.addCleanup(self._stop_async_dhclient)
|
||||
|
||||
def _start_async_dhclient(self):
|
||||
cmd = ["dhclient", '--no-pid', '-d', self.port.name]
|
||||
self.dhclient_async = async_process.AsyncProcess(
|
||||
cmd, run_as_root=True, namespace=self.namespace)
|
||||
self.dhclient_async.start()
|
||||
|
||||
def _stop_async_dhclient(self):
|
||||
if not self.dhclient_async:
|
||||
return
|
||||
try:
|
||||
self.dhclient_async.stop()
|
||||
except async_process.AsyncProcessException:
|
||||
# If it was already stopped than we don't care about it
|
||||
pass
|
||||
|
||||
@property
|
||||
def ipv6(self):
|
||||
@ -129,12 +155,33 @@ class FakeFullstackMachine(machine_fixtures.FakeMachineBase):
|
||||
def ip_cidr(self):
|
||||
return self._ip_cidr
|
||||
|
||||
def ip_configured(self):
|
||||
for port_ip in self.port.addr.list():
|
||||
if port_ip.get('cidr') == self.ip_cidr:
|
||||
return True
|
||||
return False
|
||||
|
||||
def gateway_configured(self):
|
||||
gateway_info = self.port.route.get_gateway()
|
||||
if not gateway_info:
|
||||
return False
|
||||
return gateway_info.get('gateway') == self.gateway_ip
|
||||
|
||||
def block_until_boot(self):
|
||||
utils.wait_until_true(
|
||||
lambda: (self.safe_client.client.show_port(self.neutron_port['id'])
|
||||
['port']['status'] == 'ACTIVE'),
|
||||
sleep=3)
|
||||
|
||||
def block_until_dhcp_config_done(self):
|
||||
utils.wait_until_true(
|
||||
lambda: self.ip_configured() and self.gateway_configured(),
|
||||
exception=machine_fixtures.FakeMachineException(
|
||||
"Address %s or gateway %s not configured properly on "
|
||||
"port %s" % (self.ip_cidr, self.gateway_ip, self.port.name)
|
||||
)
|
||||
)
|
||||
|
||||
def destroy(self):
|
||||
"""Destroy this fake machine.
|
||||
|
||||
|
@ -234,3 +234,33 @@ class L3AgentFixture(fixtures.Fixture):
|
||||
|
||||
def get_namespace_suffix(self):
|
||||
return self.plugin_config.DEFAULT.test_namespace_suffix
|
||||
|
||||
|
||||
class DhcpAgentFixture(fixtures.Fixture):
|
||||
|
||||
NEUTRON_DHCP_AGENT = "neutron-dhcp-agent"
|
||||
|
||||
def __init__(self, env_desc, host_desc, test_name,
|
||||
neutron_cfg_fixture, agent_cfg_fixture, namespace=None):
|
||||
super(DhcpAgentFixture, self).__init__()
|
||||
self.env_desc = env_desc
|
||||
self.host_desc = host_desc
|
||||
self.test_name = test_name
|
||||
self.neutron_cfg_fixture = neutron_cfg_fixture
|
||||
self.agent_cfg_fixture = agent_cfg_fixture
|
||||
self.namespace = namespace
|
||||
|
||||
def _setUp(self):
|
||||
self.plugin_config = self.agent_cfg_fixture.config
|
||||
|
||||
config_filenames = [self.neutron_cfg_fixture.filename,
|
||||
self.agent_cfg_fixture.filename]
|
||||
self.process_fixture = self.useFixture(
|
||||
ProcessFixture(
|
||||
test_name=self.test_name,
|
||||
process_name=self.NEUTRON_DHCP_AGENT,
|
||||
exec_name=self.NEUTRON_DHCP_AGENT,
|
||||
config_filenames=config_filenames,
|
||||
namespace=self.namespace
|
||||
)
|
||||
)
|
||||
|
77
neutron/tests/fullstack/test_dhcp_agent.py
Normal file
77
neutron/tests/fullstack/test_dhcp_agent.py
Normal file
@ -0,0 +1,77 @@
|
||||
# Copyright 2016 OVH SAS
|
||||
#
|
||||
# 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.tests.fullstack import base
|
||||
from neutron.tests.fullstack.resources import environment
|
||||
from neutron.tests.fullstack.resources import machine
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
load_tests = testlib_api.module_load_tests
|
||||
|
||||
|
||||
class TestDhcpAgent(base.BaseFullStackTestCase):
|
||||
|
||||
scenarios = [
|
||||
(constants.AGENT_TYPE_OVS,
|
||||
{'l2_agent_type': constants.AGENT_TYPE_OVS}),
|
||||
(constants.AGENT_TYPE_LINUXBRIDGE,
|
||||
{'l2_agent_type': constants.AGENT_TYPE_LINUXBRIDGE})
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
host_descriptions = [
|
||||
environment.HostDescription(
|
||||
dhcp_agent=True,
|
||||
l2_agent_type=self.l2_agent_type)]
|
||||
|
||||
env = environment.Environment(
|
||||
environment.EnvironmentDescription(
|
||||
l2_pop=False,
|
||||
arp_responder=False),
|
||||
host_descriptions)
|
||||
|
||||
super(TestDhcpAgent, self).setUp(env)
|
||||
self.project_id = uuidutils.generate_uuid()
|
||||
self._create_network_subnet_and_vm()
|
||||
|
||||
def _create_network_subnet_and_vm(self):
|
||||
self.network = self.safe_client.create_network(self.project_id)
|
||||
|
||||
self.subnet = self.safe_client.create_subnet(
|
||||
self.project_id, self.network['id'],
|
||||
cidr='10.0.0.0/24',
|
||||
gateway_ip='10.0.0.1',
|
||||
name='subnet-test',
|
||||
enable_dhcp=True)
|
||||
|
||||
self.vm = self.useFixture(
|
||||
machine.FakeFullstackMachine(
|
||||
self.environment.hosts[0],
|
||||
self.network['id'],
|
||||
self.project_id,
|
||||
self.safe_client,
|
||||
use_dhcp=True))
|
||||
self.vm.block_until_boot()
|
||||
|
||||
def test_dhcp_assignment(self):
|
||||
# First check if network was scheduled to one DHCP agent
|
||||
dhcp_agents = self.client.list_dhcp_agent_hosting_networks(
|
||||
self.network['id'])
|
||||
self.assertEqual(1, len(dhcp_agents['agents']))
|
||||
|
||||
# And check if IP and gateway config is fine on FakeMachine
|
||||
self.vm.block_until_dhcp_config_done()
|
Loading…
Reference in New Issue
Block a user