Migrate ovn router availability zone tests

Changes from original tests:
- Methods and variables set according to OSP deployer tool in base class.
- Adjust migrated imports and configuration.
- Added OVN backend check instead of configuration check.
- Replaced test UUIDs with unique ones.
- Fixed typos throughout file.

Change-Id: I8656d56d579b73dd39c3bd37c46516ada5f78a6c
This commit is contained in:
Maor Blaustein 2024-03-10 14:14:39 +02:00
parent 0348de6574
commit e33d47c604
4 changed files with 277 additions and 11 deletions

View File

@ -25,7 +25,7 @@ WhiteboxNeutronPluginOptions = [
cfg.StrOpt('openstack_type',
default='devstack',
help='Type of openstack deployment, '
'e.g. devstack, tripeo, podified'),
'e.g. devstack, podified'),
cfg.StrOpt('pki_private_key',
default='/etc/pki/tls/private/ovn_controller.key',
help='File with private key. Need for TLS-everywhere '
@ -86,5 +86,9 @@ WhiteboxNeutronPluginOptions = [
help='ssh private key file path for overcloud nodes access.'),
cfg.StrOpt('neutron_config',
default='/etc/neutron/neutron.conf',
help='Path to neutron configuration file.')
help='Path to neutron configuration file.'),
cfg.IntOpt('ovn_max_controller_gw_ports_per_router',
default=1,
help='The number of network nodes used '
'for the OVN router HA.')
]

View File

@ -59,8 +59,19 @@ class BaseTempestWhiteboxTestCase(base.BaseTempestTestCase):
sriov_agents = [
agent for agent in agents if 'sriov' in agent['binary']]
cls.has_sriov_support = True if sriov_agents else False
# deployer tool dependent variables
if WB_CONF.openstack_type == 'devstack':
cls.master_node_client = cls.get_node_client('localhost')
cls.master_cont_cmd_executor = cls.run_on_master_controller
cls.neutron_api_prefix = ''
cls.neutron_conf = WB_CONF.neutron_config
elif WB_CONF.openstack_type == 'podified':
# NOTE(mblue): add podified option
pass
else:
LOG.warning(("Unrecognized deployer tool '{}', plugin supports "
"openstack_type as devstack/podified."
.format(WB_CONF.openstack_type)))
@classmethod
def run_on_master_controller(cls, cmd):

View File

@ -26,7 +26,7 @@ CONF = config.CONF
WB_CONF = config.CONF.whitebox_neutron_plugin_options
class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
class OvsAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
credentials = ['primary', 'admin']
required_extensions = ['network_availability_zone',
@ -34,7 +34,7 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
@classmethod
def resource_setup(cls):
super(NeutronAvaliabilityzonesTest, cls).resource_setup()
super(OvsAvaliabilityzonesTest, cls).resource_setup()
cls.client = cls.os_adm.network_client
cls.get_neutron_agent_availability_zones()
if not cls.AZs_list:
@ -64,10 +64,8 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
cls.l3_agent_list[agent.get('host')] = az
else:
cls.dhcp_agent_list[agent.get('host')] = az
cmd = 'sudo podman exec neutron_api crudini --get {} DEFAULT {{}}' \
'|| echo'.format(WB_CONF.neutron_config)
if WB_CONF.openstack_type == 'devstack':
cmd_executor = cls.run_on_master_controller
cmd = '{} crudini --get {} DEFAULT {{}} || echo'.format(
cls.neutron_api_prefix, cls.neutron_conf)
def integer(int_var):
try:
@ -90,7 +88,7 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
'default_availability_zones': array}
for param, func in agent_params.items():
cls.agent_conf[param] = func(
cmd_executor(cmd.format(param)))
cls.master_cont_cmd_executor(cmd.format(param)))
def _check_az_router(self, az, router):
"""Check if a router was deployed over the agents
@ -152,8 +150,7 @@ class NeutronAvaliabilityzonesTest(base.BaseTempestWhiteboxTestCase):
az_hosts.append(agent_host)
def test_resource_namespace():
node_name = host.split('.')[0] + (
'.ctlplane' if WB_CONF.openstack_type == 'tripleo' else '')
node_name = host.split('.')[0]
netns = self.get_node_client(node_name).exec_command('ip netns')
netspaces = []
for ns in netns.split('\n'):

View File

@ -0,0 +1,254 @@
# Copyright 2024 Red Hat, Inc.
# 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 neutron_tempest_plugin import config
from oslo_log import log
from tempest.lib import decorators
from tempest.lib import exceptions
from whitebox_neutron_tempest_plugin.tests.scenario import base
AZ_SUPPORTED_AGENTS = ['OVN Controller Gateway agent']
CONF = config.CONF
WB_CONF = CONF.whitebox_neutron_plugin_options
LOG = log.getLogger(__name__)
class OvnAvaliabilityzonesTest(
base.TrafficFlowTest, base.BaseTempestTestCaseOvn):
credentials = ['primary', 'admin']
required_extensions = ['network_availability_zone',
'availability_zone', 'router_availability_zone']
@classmethod
def resource_setup(cls):
super(OvnAvaliabilityzonesTest, cls).resource_setup()
if not cls.has_ovn_support:
raise cls.skipException(
"These availability zone tests are meant for OVN only.")
cls.client = cls.os_adm.network_client
cls.get_ovn_controller_gw_agent_availability_zones()
if not cls.AZs_list:
raise cls.skipException("No availability zones configured")
@classmethod
def get_ovn_controller_gw_agent_availability_zones(cls):
"""Obtain availability_zones for OVN Controller Gateway
Only OVN Controller Gateway supports router availability_zone
"""
body = cls.client.list_agents()
agents = body['agents']
cls.AZs_list = []
cls.ovn_controller_gw_agent_list = {}
cls.agent_conf = {}
for agent in agents:
if agent.get('agent_type') in AZ_SUPPORTED_AGENTS:
az = agent.get('availability_zone')
if (az and (az not in cls.AZs_list)):
cls.AZs_list.append(az)
cls.ovn_controller_gw_agent_list[
agent.get('host').replace('"', '').split('.')[0]] = az
cmd = '{} crudini --get {} DEFAULT {{}} || echo'.format(
cls.neutron_api_prefix, cls.neutron_conf)
def array(list_var):
return_list = list_var.strip().split(',')
if return_list == ['']:
LOG.debug('Empty string can not be splitted')
return []
return return_list
agent_params = {'default_availability_zones': array}
for param, func in agent_params.items():
cls.agent_conf[param] = func(
cls.master_cont_cmd_executor(cmd.format(param)))
# TODO(ccamposr): Hard-coded value, should be extracted from the env
# it could be extracted from ovn database
cls.max_controller_gw_ports_per_router = \
WB_CONF.ovn_max_controller_gw_ports_per_router
def _check_az_router(self, az, router):
"""Check that resource is correctly spawned in required AZs
Function checks that router is assigned to the relevant
network nodes.
There are several possible scenarios:
Scenario 1 (not enough network nodes in the AZs configured):
Resource is expected to be deployed over several (lets say 3)
network nodes, but there are less or equal amount of them available
in the provided list of availability zones. In this case the resource
should be spawned over all the network nodes,
Scenario 2 (not enough AZs):
Resource is expected to be deployed over several (lets say 3) network
nodes, but there are less or equal amount of availability zones are
provided as a hint. In this case there should be at least one network
node from each availability zone to host the resource and the all other
network nodes will be chosen randomly
Scenario 3 (too many AZs):
Resource is expected to be deployed over several (lets say 3)
network nodes, but more (lets say 4) availability zones are provided
as a hint. In this case the resource should be spawned over 3 random
availability zones (one network node per zone).
"""
resource_list = self.ovn_controller_gw_agent_list
router_port = self.os_admin.network_client.list_ports(
device_owner=constants.DEVICE_OWNER_ROUTER_GW,
device_id=router['id'])['ports'][0]
chassis_list = self.get_router_gateway_chassis_list(
router_port['id'])
resource_hosts = list(self.get_router_gateway_chassis_by_id(iden)
for iden in chassis_list)
az_hosts = []
for agent_host, agent_zone in resource_list.items():
if agent_zone in az:
az_hosts.append(agent_host)
# Scenario 1 from docstring
if self.max_controller_gw_ports_per_router >= len(az_hosts):
# Check that the router is deployed over all the az_hosts
for host in az_hosts:
self.assertIn(host, resource_hosts)
# Check that router isn't deployed over other az hosts
self.assertEqual(len(az_hosts), len(resource_hosts))
# Scenario 2 from docstring
elif self.max_controller_gw_ports_per_router >= len(az):
tmp_zones = []
# Check that the router is only deployed over az hosts
for host in resource_hosts:
self.assertIn(host, az_hosts)
tmp_zones.append(resource_list[host])
# Check that it is deployed over all the azs
self.assertEqual(set(az).symmetric_difference(set(tmp_zones)),
set())
# Check that the max redundancy is reached
self.assertEqual(len(resource_hosts),
self.max_controller_gw_ports_per_router)
# Scenario 3 from docstring
else:
tmp_zones = []
# Check that the router is only deployed over az hosts
for host in resource_hosts:
self.assertIn(host, az_hosts)
tmp_zones.append(resource_list[host])
# Check that it is deployed only th one instance per az
self.assertEqual(len(tmp_zones), len(set(tmp_zones)))
# Check that the max redundancy is reached
self.assertEqual(len(resource_hosts),
self.max_controller_gw_ports_per_router)
@decorators.idempotent_id('bc0439e3-f503-4ebc-9194-0babe155a406')
def test_router_created_in_single_az_with_not_enough_agents(self):
"""Verify that router is deployed over one AZ with NOT enough network
nodes
Router should be deployed over all the network nodes in
the availability zone
"""
az_host_counter = {}
for tmp_az in self.ovn_controller_gw_agent_list.values():
tmp_counter = az_host_counter.get(tmp_az, 0) + 1
az_host_counter[tmp_az] = tmp_counter
for tmp_az, counter in az_host_counter.items():
if counter < self.max_controller_gw_ports_per_router:
az = tmp_az
break
else:
raise self.skipException('No availability zone with not enough '
'resources available')
router = self.create_router_by_client(availability_zone_hints=[az])
self._check_az_router([az], router)
@decorators.idempotent_id('584c2d71-282f-4ea1-8360-77bde6cdcbf1')
def test_router_created_in_single_az_with_enough_agents(self):
"""Verify that router is deployed over one AZ with enough
network nodes
Router should be deployed over the required amount of the
network nodes in the specified availability zone
"""
az_host_counter = {}
for tmp_az in self.ovn_controller_gw_agent_list.values():
tmp_counter = az_host_counter.get(tmp_az, 0) + 1
az_host_counter[tmp_az] = tmp_counter
for tmp_az, counter in az_host_counter.items():
if counter >= self.max_controller_gw_ports_per_router:
az = tmp_az
break
else:
raise self.skipException('No availability zone with enough '
'resources available')
router = self.create_router_by_client(availability_zone_hints=[az])
self._check_az_router([az], router)
@decorators.idempotent_id('b3c42bd9-6888-4397-88f1-c624048da138')
def test_router_created_in_all_azs(self):
"""Verify that router is created in all available AZs
If the amount of availability zones is more than the
OVN gw max number the test will be skipped as the router
will not be created in all the available AZs
"""
if len(self.AZs_list) == 1:
raise self.skipException("Only one availability zone configured "
"but multiple zones required")
router = self.create_router_by_client(
availability_zone_hints=self.AZs_list)
self._check_az_router(self.AZs_list, router)
@decorators.idempotent_id('e9a9ae58-19aa-41e3-bfb6-5c6881db5a17')
def test_router_created_in_several_azs(self):
"""Verify that router is created in several but not all the AZs
Test will be executed in the environments with more than 1 OVN gw
per router and with more than 2 availability zones
"""
if len(self.AZs_list) <= 2:
raise self.skipException("More than 2 AZs required")
if self.max_controller_gw_ports_per_router <= 1:
raise self.skipException("More than 1 OVN gw required")
amount = min(self.max_controller_gw_ports_per_router,
len(self.AZs_list) - 1)
router = self.create_router_by_client(
availability_zone_hints=self.AZs_list[:amount])
self._check_az_router(self.AZs_list[:amount], router)
@decorators.idempotent_id('b2ea83d8-7b2c-4c58-9c3f-d87cc04b4f1b')
def test_router_created_in_default_azs(self):
"""Verify that router is correctly spawned in default AZs
If there is no parameter specified for default AZs, all the AZs
are recognized as default.
"""
router = self.create_router_by_client()
azs = self.agent_conf['default_availability_zones'] or self.AZs_list
self._check_az_router(azs, router)
@decorators.idempotent_id('64bfe42a-3420-4922-8c06-1b87a3d10da2')
def test_router_creation_failed_for_not_existing_az(self):
"""Verify that router creation failed if AZ doesn't exist"""
tmp_az_list = []
for az in self.AZs_list:
tmp_az_list.append(az)
tmp_az_list.append('not_existing_az')
self.assertRaises(exceptions.NotFound,
self.create_router_by_client,
availability_zone_hints=tmp_az_list)