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:
parent
0348de6574
commit
e33d47c604
@ -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.')
|
||||
]
|
||||
|
@ -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):
|
||||
|
@ -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'):
|
||||
|
@ -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)
|
Loading…
Reference in New Issue
Block a user