From f38688cfec47146e66a1ec914727ea3e9e4c34a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Sat, 10 Dec 2016 10:27:27 +0000 Subject: [PATCH] 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 --- neutron/tests/fullstack/resources/config.py | 48 ++++++++++++ .../tests/fullstack/resources/environment.py | 35 ++++++++- neutron/tests/fullstack/resources/machine.py | 61 +++++++++++++-- neutron/tests/fullstack/resources/process.py | 30 ++++++++ neutron/tests/fullstack/test_dhcp_agent.py | 77 +++++++++++++++++++ 5 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 neutron/tests/fullstack/test_dhcp_agent.py diff --git a/neutron/tests/fullstack/resources/config.py b/neutron/tests/fullstack/resources/config.py index 166653317fc..84c67dea3f9 100644 --- a/neutron/tests/fullstack/resources/config.py +++ b/neutron/tests/fullstack/resources/config.py @@ -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) diff --git a/neutron/tests/fullstack/resources/environment.py b/neutron/tests/fullstack/resources/environment.py index fbae253e268..ce171d1d3f8 100644 --- a/neutron/tests/fullstack/resources/environment.py +++ b/neutron/tests/fullstack/resources/environment.py @@ -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'] diff --git a/neutron/tests/fullstack/resources/machine.py b/neutron/tests/fullstack/resources/machine.py index 2c06aa80112..58afd24a993 100644 --- a/neutron/tests/fullstack/resources/machine.py +++ b/neutron/tests/fullstack/resources/machine.py @@ -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. diff --git a/neutron/tests/fullstack/resources/process.py b/neutron/tests/fullstack/resources/process.py index 27e2e83e278..ac12b2784b7 100644 --- a/neutron/tests/fullstack/resources/process.py +++ b/neutron/tests/fullstack/resources/process.py @@ -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 + ) + ) diff --git a/neutron/tests/fullstack/test_dhcp_agent.py b/neutron/tests/fullstack/test_dhcp_agent.py new file mode 100644 index 00000000000..9dd1fc3ce2c --- /dev/null +++ b/neutron/tests/fullstack/test_dhcp_agent.py @@ -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()