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:
Sławek Kapłoński 2016-12-10 10:27:27 +00:00
parent 920ddeaf58
commit f38688cfec
5 changed files with 242 additions and 9 deletions

View File

@ -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)

View File

@ -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']

View File

@ -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.

View File

@ -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
)
)

View 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()