
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
444 lines
16 KiB
Python
444 lines
16 KiB
Python
# Copyright 2015 Red Hat, Inc.
|
|
#
|
|
# 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 signal
|
|
|
|
import fixtures
|
|
from neutron_lib import constants
|
|
from neutronclient.common import exceptions as nc_exc
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from neutron.common import utils as common_utils
|
|
from neutron.conf import quota as quota_conf
|
|
from neutron.tests.common.exclusive_resources import ip_address
|
|
from neutron.tests.common import net_helpers
|
|
from neutron.tests.fullstack.resources import config
|
|
from neutron.tests.fullstack.resources import process
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class EnvironmentDescription:
|
|
"""A set of characteristics of an environment setup.
|
|
|
|
Does the setup, as a whole, support tunneling? How about l2pop?
|
|
"""
|
|
def __init__(self, network_type='vxlan', l2_pop=True, qos=False,
|
|
mech_drivers='openvswitch',
|
|
service_plugins='router', arp_responder=False,
|
|
agent_down_time=75, router_scheduler=None,
|
|
global_mtu=constants.DEFAULT_NETWORK_MTU,
|
|
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,
|
|
enable_traditional_dhcp=True, local_ip_ext=False,
|
|
quota_driver=quota_conf.QUOTA_DB_DRIVER,
|
|
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
|
|
self.log = log
|
|
self.network_range = None
|
|
self.mech_drivers = mech_drivers
|
|
self.arp_responder = arp_responder
|
|
self.agent_down_time = agent_down_time
|
|
self.router_scheduler = router_scheduler
|
|
self.global_mtu = global_mtu
|
|
self.service_plugins = service_plugins
|
|
self.debug_iptables = debug_iptables
|
|
self.report_bandwidths = report_bandwidths
|
|
self.has_placement = has_placement
|
|
self.placement_port = placement_port
|
|
self.dhcp_scheduler_class = dhcp_scheduler_class
|
|
if self.qos:
|
|
self.service_plugins += ',qos'
|
|
if self.log:
|
|
self.service_plugins += ',log'
|
|
self.ml2_extension_drivers = ml2_extension_drivers
|
|
self.api_workers = api_workers
|
|
self.enable_traditional_dhcp = enable_traditional_dhcp
|
|
self.local_ip_ext = local_ip_ext
|
|
if self.local_ip_ext:
|
|
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):
|
|
return self.network_type in ('vxlan', 'gre')
|
|
|
|
def __str__(self):
|
|
return '{}'.format(vars(self))
|
|
|
|
|
|
class HostDescription:
|
|
"""A set of characteristics of an environment Host.
|
|
|
|
What agents should the host spawn? What mode should each agent operate
|
|
under?
|
|
"""
|
|
def __init__(self, l3_agent=False, dhcp_agent=False,
|
|
l2_agent_type=constants.AGENT_TYPE_OVS,
|
|
firewall_driver='noop', availability_zone=None,
|
|
l3_agent_mode=None,
|
|
l3_agent_extensions=None,
|
|
segmented_physnet=False):
|
|
self.l2_agent_type = l2_agent_type
|
|
self.l3_agent = l3_agent
|
|
self.dhcp_agent = dhcp_agent
|
|
self.firewall_driver = firewall_driver
|
|
self.availability_zone = availability_zone
|
|
self.l3_agent_mode = l3_agent_mode
|
|
self.l3_agent_extensions = l3_agent_extensions
|
|
self.segmented_physnet = segmented_physnet
|
|
|
|
def __str__(self):
|
|
return '{}'.format(vars(self))
|
|
|
|
|
|
class Host(fixtures.Fixture):
|
|
"""The Host class models a physical host running agents, all reporting with
|
|
the same hostname.
|
|
|
|
OpenStack installers or administrators connect compute nodes to the
|
|
physical tenant network by connecting the provider bridges to their
|
|
respective physical NICs. Or, if using tunneling, by configuring an
|
|
IP address on the appropriate physical NIC. The Host class does the same
|
|
with the connect_* methods.
|
|
|
|
TODO(amuller): Add restart method that will restart all of the agents on
|
|
this host.
|
|
"""
|
|
|
|
def __init__(self, env_desc, host_desc, test_name,
|
|
neutron_config, central_bridge):
|
|
self.env_desc = env_desc
|
|
self.host_desc = host_desc
|
|
self.test_name = test_name
|
|
self.neutron_config = neutron_config
|
|
self.central_bridge = central_bridge
|
|
self.host_namespace = None
|
|
self.agents = {}
|
|
|
|
def _setUp(self):
|
|
self.local_ip = self.allocate_local_ip()
|
|
|
|
if self.host_desc.l2_agent_type == constants.AGENT_TYPE_OVS:
|
|
self.setup_host_with_ovs_agent()
|
|
elif self.host_desc.l2_agent_type == constants.AGENT_TYPE_NIC_SWITCH:
|
|
self.setup_host_with_sriov_agent()
|
|
if self.host_desc.l3_agent:
|
|
self.l3_agent = self.useFixture(
|
|
process.L3AgentFixture(
|
|
self.env_desc, self.host_desc,
|
|
self.test_name,
|
|
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 __repr__(self) -> str:
|
|
return 'Host description {}, environment description: {}'.format(
|
|
self.host_desc, self.env_desc)
|
|
|
|
def setup_host_with_ovs_agent(self):
|
|
agent_cfg_fixture = config.OVSConfigFixture(
|
|
self.env_desc, self.host_desc, self.neutron_config.temp_dir,
|
|
self.local_ip, test_name=self.test_name)
|
|
self.useFixture(agent_cfg_fixture)
|
|
|
|
self.br_phys = self.useFixture(
|
|
net_helpers.OVSBridgeFixture(
|
|
agent_cfg_fixture.get_br_phys_name())).bridge
|
|
if self.env_desc.tunneling_enabled:
|
|
self.useFixture(
|
|
net_helpers.OVSBridgeFixture(
|
|
agent_cfg_fixture.get_br_tun_name())).bridge
|
|
self.connect_to_central_network_via_tunneling()
|
|
else:
|
|
self.connect_to_central_network_via_vlans(self.br_phys)
|
|
|
|
self.ovs_agent = self.useFixture(
|
|
process.OVSAgentFixture(
|
|
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(
|
|
self.env_desc, self.host_desc,
|
|
self.neutron_config.temp_dir,
|
|
self.ovs_agent.agent_cfg_fixture.get_br_int_name()))
|
|
|
|
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_sriov_agent(self):
|
|
agent_cfg_fixture = config.SRIOVConfigFixture(
|
|
self.env_desc, self.host_desc, self.neutron_config.temp_dir,
|
|
self.local_ip)
|
|
self.useFixture(agent_cfg_fixture)
|
|
self.sriov_agent = self.useFixture(
|
|
process.SRIOVAgentFixture(
|
|
self.env_desc, self.host_desc,
|
|
self.test_name, self.neutron_config, agent_cfg_fixture))
|
|
|
|
def _connect_ovs_port(self, cidr_address):
|
|
ovs_device = self.useFixture(
|
|
net_helpers.OVSPortFixture(
|
|
bridge=self.central_bridge,
|
|
namespace=self.host_namespace)).port
|
|
# NOTE: This sets an IP address on the host's root namespace
|
|
# which is cleaned up when the device is deleted.
|
|
ovs_device.addr.add(cidr_address)
|
|
return ovs_device
|
|
|
|
def connect_namespace_to_control_network(self):
|
|
self.host_port = self._connect_ovs_port(
|
|
common_utils.ip_to_cidr(self.local_ip, 24)
|
|
)
|
|
self.host_port.link.set_up()
|
|
|
|
def connect_to_central_network_via_tunneling(self):
|
|
veth_1, veth_2 = self.useFixture(
|
|
net_helpers.VethFixture()).ports
|
|
|
|
# NOTE: This sets an IP address on the host's root namespace
|
|
# which is cleaned up when the device is deleted.
|
|
veth_1.addr.add(common_utils.ip_to_cidr(self.local_ip, 32))
|
|
|
|
veth_1.link.set_up()
|
|
veth_2.link.set_up()
|
|
self.tunnel_device = veth_1
|
|
|
|
def connect_to_central_network_via_vlans(self, host_data_bridge):
|
|
# If using VLANs as a segmentation device, it's needed to connect
|
|
# a provider bridge to a centralized, shared bridge.
|
|
source, destination = net_helpers.create_patch_ports(
|
|
self.central_bridge, host_data_bridge)
|
|
self.internal_port = destination
|
|
|
|
def allocate_local_ip(self):
|
|
if not self.env_desc.network_range:
|
|
return str(self.useFixture(
|
|
ip_address.ExclusiveIPAddress(
|
|
'240.0.0.1', '240.255.255.254')).address)
|
|
return str(self.useFixture(
|
|
ip_address.ExclusiveIPAddress(
|
|
str(self.env_desc.network_range[2]),
|
|
str(self.env_desc.network_range[-2]))).address)
|
|
|
|
def get_bridge(self):
|
|
if "ovs" in self.agents.keys():
|
|
return self.ovs_agent.br_int
|
|
|
|
def disconnect(self):
|
|
if self.env_desc.tunneling_enabled:
|
|
self.tunnel_device.addr.flush(4)
|
|
else:
|
|
self.br_phys.delete_port(self.internal_port)
|
|
LOG.info(f'Host {self.hostname} disconnected.')
|
|
|
|
def kill(self, parent=None):
|
|
# First kill all the agent to prevent a graceful shutdown
|
|
for agent_name, agent in self.agents.items():
|
|
agent.stop(kill_signal=signal.SIGKILL)
|
|
LOG.info(f'Agents on host {self.hostname} killed.')
|
|
|
|
self.shutdown(parent)
|
|
|
|
def shutdown(self, parent=None):
|
|
self.cleanUp()
|
|
|
|
# Remove cleanup function from parent because it can't be called twice
|
|
if parent:
|
|
parent._cleanups._cleanups.remove(
|
|
(self.cleanUp, (), {})
|
|
)
|
|
|
|
LOG.info(f'Host {self.hostname} shut down.')
|
|
|
|
@property
|
|
def hostname(self):
|
|
return self.neutron_config.config.DEFAULT.host
|
|
|
|
@property
|
|
def l3_agent(self):
|
|
return self.agents['l3']
|
|
|
|
@l3_agent.setter
|
|
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']
|
|
|
|
@ovs_agent.setter
|
|
def ovs_agent(self, agent):
|
|
self.agents['ovs'] = agent
|
|
|
|
@property
|
|
def sriov_agent(self):
|
|
return self.agents['sriov']
|
|
|
|
@sriov_agent.setter
|
|
def sriov_agent(self, agent):
|
|
self.agents['sriov'] = agent
|
|
|
|
@property
|
|
def l2_agent(self):
|
|
if self.host_desc.l2_agent_type == constants.AGENT_TYPE_OVS:
|
|
return self.ovs_agent
|
|
if self.host_desc.l2_agent_type == constants.AGENT_TYPE_NIC_SWITCH:
|
|
return self.sriov_agent
|
|
|
|
|
|
class Environment(fixtures.Fixture):
|
|
"""Represents a deployment topology.
|
|
|
|
Environment is a collection of hosts. It starts a Neutron server
|
|
and a parametrized number of Hosts, each a collection of agents.
|
|
The Environment accepts a collection of HostDescription, each describing
|
|
the type of Host to create.
|
|
"""
|
|
|
|
def __init__(self, env_desc, hosts_desc):
|
|
"""Initialize Environment
|
|
|
|
:param env_desc: An EnvironmentDescription instance.
|
|
:param hosts_desc: A list of HostDescription instances.
|
|
"""
|
|
|
|
super().__init__()
|
|
self.env_desc = env_desc
|
|
self.hosts_desc = hosts_desc
|
|
self.hosts = []
|
|
|
|
def wait_until_env_is_up(self):
|
|
common_utils.wait_until_true(
|
|
self._processes_are_ready,
|
|
timeout=180,
|
|
sleep=10)
|
|
|
|
def _processes_are_ready(self):
|
|
try:
|
|
running_agents = self.neutron_server.client.list_agents()['agents']
|
|
agents_count = sum(len(host.agents) for host in self.hosts)
|
|
return len(running_agents) == agents_count
|
|
except nc_exc.NeutronClientException:
|
|
return False
|
|
|
|
def get_host_by_name(self, hostname):
|
|
return next(host for host in self.hosts if host.hostname == hostname)
|
|
|
|
def _create_host(self, host_desc):
|
|
temp_dir = self.useFixture(fixtures.TempDir()).path
|
|
neutron_config = config.NeutronConfigFixture(
|
|
self.env_desc, host_desc, temp_dir,
|
|
cfg.CONF.database.connection, self.rabbitmq_environment)
|
|
self.useFixture(neutron_config)
|
|
|
|
return self.useFixture(
|
|
Host(self.env_desc,
|
|
host_desc,
|
|
self.test_name,
|
|
neutron_config,
|
|
self.central_bridge))
|
|
|
|
def _setUp(self):
|
|
self.temp_dir = self.useFixture(fixtures.TempDir()).path
|
|
|
|
# we need this bridge before rabbit and neutron service will start
|
|
self.central_bridge = self.useFixture(
|
|
net_helpers.OVSBridgeFixture('cnt-data')).bridge
|
|
|
|
# Get rabbitmq address (and cnt-data network)
|
|
rabbitmq_ip_address = "127.0.0.1"
|
|
self.rabbitmq_environment = self.useFixture(
|
|
process.RabbitmqEnvironmentFixture(host=rabbitmq_ip_address)
|
|
)
|
|
|
|
plugin_cfg_fixture = self.useFixture(
|
|
config.ML2ConfigFixture(
|
|
self.env_desc, self.hosts_desc, self.temp_dir,
|
|
self.env_desc.network_type))
|
|
neutron_cfg_fixture = self.useFixture(
|
|
config.NeutronConfigFixture(
|
|
self.env_desc, None, self.temp_dir,
|
|
cfg.CONF.database.connection, self.rabbitmq_environment))
|
|
self.neutron_server = self.useFixture(
|
|
process.NeutronServerFixture(
|
|
self.env_desc, None,
|
|
self.test_name, neutron_cfg_fixture, plugin_cfg_fixture))
|
|
|
|
if self.env_desc.has_placement:
|
|
placement_cfg_fixture = self.useFixture(
|
|
config.PlacementConfigFixture(self.env_desc, self.hosts_desc,
|
|
self.temp_dir)
|
|
)
|
|
self.placement = self.useFixture(
|
|
process.PlacementFixture(
|
|
self.env_desc, self.hosts_desc, self.test_name,
|
|
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()
|